锁+关键字
2021-07-11 22:48:13 0 举报
AI智能生成
锁
作者其他创作
大纲/内容
Unsafe(CAS核心类)
一、简介
Unsafe类相当于是一个java语言中的后门类,**提供了硬件级别的原子操作**
二、Unsafe Api
arrayBaseOffset
获取数组的基础偏移量
arrayIndexScale
获取数组中元素的偏移间隔,要获取对应所以的元素,将索引号和该值相乘,获得数组中指定角标元素的偏移量
getObjectVolatile
获取对象上的属性值或者数组中的元素
getObject
获取对象上的属性值或者数组中的元素,已过时
putOrderedObject
设置对象的属性值或者数组中某个角标的元素,不保证线程间即时可见性,更高效
putObjectVolatile
设置对象的属性值或者数组中某个角标的元素
putObject
设置对象的属性值或者数组中某个角标的元素,已过时
三、问题
1、ABA
问题
CAS是比较值,如果值相等则变换。此处可能存在这样的情况,线程1获取变量值为5,线程2将值改为10,线程3再将值改回5。对于线程1,变量的值没有变,但对于计数等后续操作是不正确的
解决思路
可以参考数据库乐观锁机制,加版本号,变量更新时版本号会改变。通过比较版本号代替比较变量值。与集合的Fast-Fail机制类似,检查modCount值是否一致
Volatile
死锁
发生死锁的必要条件
互斥条件
在一段时间内,一种资源只能被一个进行锁使用
请求和保持条件
进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
不可抢占条件
进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放
循环等待条件
发生死锁时,必然存在一个进程——资源的循环链
定位死锁的方法
1、JPS+JStack 进程pid
2、jconsole检测死锁
避免死锁的方法
1、在线程使用锁对象时,采用固定加锁的顺序,可以使用Hash值的大小来确定加锁的先后
2、尽可能缩减加锁的范围,等到操作共享变量的时候才加锁
3、使用可释放的定时锁(一段时间申请不到锁的权限了,直接释放掉)
Java对象头
普通对象
Object Header(64 bits)
Mark Word (32 bits)
1、hashCode
2、age
3、biased_lock(是否是偏向锁)
0 否
1 是
4、00、01、10、11(加锁状态)
State(表示是正常还是阻塞状态)
Klass Word (32 bits)
数组对象
Object Header(96 bits)
Mark Word (32 bits)
Klass Word (32 bits)
array length (32 bits)
synchronized原理
Monitor概念(重量级锁)
Monitor(锁)
简介
监视器/管程
每个Java对象都可以关联一个Monitor对象,当使用synchronized给对象加锁(重量锁)后,该对象头的Mark Word中就被设置指向Monitor对象的指针
组成
WaitSet
EntryList
阻塞队列
Owner
所有者
加锁原理
1、Obj对象的MarkWord指向Monitor
2、MarkWord全部改变为 --> ptr_to_heavyweight_monitor:30 10
3、线程A拿到锁,成为Monitor的Owner
4、其他线程(B、C)进入Monitor的阻塞队列EntryList
5、线程A执行完毕,取消指向Owner,此时唤醒阻塞队列中的线程,B和C进行竞争锁(非公平竞争)
6、假设线程B竞争到了锁,那么便是此Monitor的新Owner
synchronized优化
轻量级锁
使用场景
如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(无需竞争),那么可以使用轻量级锁来优化
轻量级锁对使用者是透明的,语法仍是synchronized
原理
1、线程创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
2、让锁记录中的Object reference指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录
ptr_to_lock_record:30 00
3、CAS操作,替换Object的Mark Word
3.1、如果cas替换成功,对象头中存储了锁记录地址和状态00,表示该线程给对象加锁
3.2、如果cas操作失败
(1)如果是其他线程已经持有该Object的轻量级锁,表示有竞争,进入锁膨胀过程
(2)如果自己执行了synchronized锁重入,那么再增加一条Lock Record作为重入的计数
4、退出synchronized代码块
4.1、当退出synchronized代码块(解锁时),如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
4.2、当退出synchronized代码块(解锁时),锁记录值不为null,这时使用cas将Mark Word的值恢复给对象头
成功,解锁成功
失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁的解锁流程
锁膨胀
简述
如果在尝试加轻量级锁的过程中,CAS操作失败,有可能是有其他线程为此对象加上了轻量级锁,这时需要锁膨胀,将轻量级锁变为重量级锁
锁膨胀流程
1、Thread 1 进行轻量级加锁时,发现Thread 0已经给该对象加了轻量级锁
2、Thread 1 为该对象申请Monitor锁,让Object指向重量级锁
3、Thread 1 进入Monitor的EntryList BLOCKED
4、当Thread 0 退出同步块解锁时,使用cas将Mark Word值恢复给对象头时会失败,此时会进入重量级锁解锁流程
(1)按照Monitor地址找到Monitor对象
(2)设置Owner为null
(3)唤醒EntryList中BLOCKED线程
自旋优化
简介
重量级锁竞争的时候,可使用自旋来进行优化,如果当前线程自旋成功(持锁线程退出同步块,释放了锁),当前线程可以避免阻塞
可参考jdk1.7ConcurrentHashMap
注意
1、java6之后自旋锁是自适应的,比如对象刚刚一次的自选操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能
2、自旋会占用cpu时间,多核CPU自旋才能发挥优势
3、java7以后不能控制是否开启自旋功能
偏向锁优化
简介
让对象偏向当前线程
原理
轻量级锁在没有竞争时,每次冲入仍然需要执行CAS操作
java6引入偏向锁在进一步优化;只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己就表示无竞争,不用重新CAS
以后只要不发生竞争,这个对象就归该线程所有
偏向状态
一个对象创建时
1、如果开启了创建锁(默认开启),那么对象创建后,Mark Word值为0x05即最后三位为101,这时它的thread、epoch、age都为0
2、偏向锁是默认延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加JVM参数 -XX:BiaseLockingStartupDelay=0来禁用延迟
-XX:-UseBiaseLocking 禁用偏向锁
3、如果没有开启偏向锁,对象创建后,Mark Word值为0x01,即最后三位为001,这时它的hashCode、age都是0,第一次用到hashCode才会赋值
4、调用了hashCode()方法后,偏向锁失效,直接轻量级锁,因为对象头没有空间存放hashCode了
hashCode会存在轻量级锁的栈帧的锁记录
hashCode会存在重量级锁的Monitor对象中,解锁时会还原回去
撤销->其他线程使用对象
当有其他线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
撤销->调用wait/notify
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会偏向T2,重偏向会重置对象的Thread ID
当撤销偏向锁阈值超过20次后,jvm会觉得是否偏向错了,于是会在给这些对象加锁时重新偏向
批量撤销
当撤销偏向锁阈值超过40此,jvm会认为自己偏向错了,不该偏向。所以整个类的所有对象都变为不可偏向,新建出来的类也是不可偏向的
锁消除
JIT发现这个锁对象是局部变量,不会存在共享,干脆就就将锁消除了
ReentranLock
基本语法
相对于synchronized具备的特点
1、可中断
2、可以设置超时时间
3、可以设置为公平锁
4、支持多个条件变量
与synchronized一样,都支持可重入
特性
一、可重入
同一个线程如果首次获得这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
二、可打断
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁
synchronized不可打断锁,lock是可打断锁
三、锁超时
可以设置超时时间,尝试获取所对象,如果超过了设置时间仍未获取到锁,就退出阻塞队列,释放掉自己拥有的锁
四、公平锁
ReentrantLock默认为不公平锁
可通过构造方法传入true,来创建公平锁
五、条件变量
1、synchronized中也有条件变量,当条件不满足进入WaitSet等待
2、ReentrantLocck支持多个条件变量
(1)synchronized将不满足条件的线程都放在一间休息室等消息
(2)ReentrantLock支持多个休息间,唤醒也根据休息室来唤醒
有多个waitset
使用流程
1、await前需要获得锁
2、await执行后,释放锁,进入conditionObject等待
3、await线程被唤醒去重新竞争lock锁
4、竞争lock锁成功后,从await后继续执行
六、同步模式之顺序控制
1、Wait/Notify版本实现
2、使用ReentrantLock的await/signal
3、使用LockSupport的park/unpart
锁的种类
1、公平锁/非公平锁
公平锁
多线程按照申请锁的顺序来获取锁
不公平锁
多线程获取锁的顺序并不是按照申请锁的顺序
判断条件
是否会去尝试获取锁,尝试获取锁为非公平锁
2、可重入锁
同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
3、独享锁/共享锁
独享锁
该锁一次只能被一个线程持有
ReentranLock
synchronized
共享锁
该锁可被多个线程持有
ReadWriteLock
4、互斥锁/读写锁
互斥锁
ReentranLock
读写锁
ReadWriteLock
5、乐观锁/悲观锁
乐观锁
对同一个数据的并发操作,是不会发生修改的
无锁编程,CAS算法
悲观锁
对同一个数据并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改
利用各种锁
6、分段锁
一种锁的设计,ConcurrentHashMap的Segment数组中的每一个Segment都继承了ReentranLock
7、偏向锁/轻量级锁/重量级锁
偏向锁
一段同步代码一直被一个线程访问,那么该线程自动获取偏向锁,降低获取锁的代价
轻量级锁
当锁为偏向锁时,被另一个线程访问,则会升级为轻量级锁。其他线程通过自旋的方式尝试获取锁
重量级锁
当锁为轻量级锁时,另一个线程自旋到一定次数时,还没获取到锁,此时会进入阻塞,该锁膨胀为重量级锁
8、自旋锁
进行while循环来自旋尝试获取锁,提高效率
0 条评论
下一页