ReentrantLock 基本原理
ReentrantLock底层基于AQS,其构造方法返回的就是NonfaireSync和faireSync;两种同步器都继承自Sync,Sync又继承自AQS!newReentrantLock时,返回的就是同步器,默认是非公平的;
ReentrantLock
一. 概述
ReentrantLock底层基于AQS
,其构造方法返回的就是NonfaireSync
和faireSync
;
两种同步器都继承自Sync,Sync又继承自AQS!
new ReentrantLock时,返回的就是同步器,默认是非公平的;
基本语法:
创建reentranLock对象:
上锁:
ReentrantLock的lock()方法用来让执行的线程获取锁;
try放的是临界区的代码,finally为了不管是否出现异常,都去释放ReentrantLock对象的锁;
ReentrantLock.lock 放在try 外面和里面都可以,《阿里手册》说放try的外面;
保证lock和unlock是成对出现! 最后一定要解锁!
补充:
不要将获取锁写在 try 语句块中!因为一旦 try 抛出异常未获取到锁,会导致finallly语句块中无故释放!
二. 分析
非公平锁、可重入锁、可中断锁、条件变量锁 都是基于底层 state属性(volatile修饰)和阻塞队列来配合实现的;
1. 非公平锁的加锁
1. 1 加锁成功
调用 lock()
方法,使用CAS
机制尝试将AQS底层 volatile
修饰的 state
属性从0改为1,并将当前线程设为exclusiveOwnerThread
即占有锁;
若CAS修改失败即出现竞争,则进入 acquire() 方法;
1. 2 加锁失败
- 当锁已经被占用,共享内存中的 state属性是1,compareAndSet(0,1) 会失败,CAS更改state属性失败,会进入 acquire() 方法;
- 进入else中 AQS的 acquire() 方法;
- 调用 tryAcquire() 再次尝试,如果还失败,再执行acquireQueued() ,
如果是第一次会创造一个哨兵节点Node,然后再创造一个Node节点对象关联当前线程,并把节点添加到FIFO等待队列(双向链表)中; - 节点进入队列后,会再次尝试加锁,失败后会被 park 阻塞,节点前驱节点的
waitStatus
会被置为 -1,-1表示可以去唤醒后继节点;
AQS中的acquire()方法:
假设多个线程多竞争失败,进入了FIFO等待队列:
1. 非公平锁的释放
1.1 unlock() 释放锁+唤醒线程
- finally{ } 中调用
unlock()
方法,底层调用了 release() 方法,使用 tryRelease() 将state
置为0,并将exclusiveOwnerThread
置为null,即释放锁; - tryRelease() 返回true后,判断head哨兵节点是否为null ,如果否且哨兵节点的waitStatus 也不会0(为-1),则哨兵节点的后继节点被 unParkSuccessor 唤醒 !线程就恢复了,就有机会去竞争锁;原来的节点就从队列中断开;
1.2 竞争失败
如果此时又来了一个新的线程,那么就会和队列中要被释放的线程一起竞争锁,
如果新的线程获取到了锁成为了exclusiveOwnerThread,则队列中的线程解锁失败,
在for中循环tryAcquire返回false,又被阻塞住;
2. 可重入锁
2.1 加锁(state自增)
加锁最终调用nonFairTryAcquire( 1 ), 先获取state值,
①如果state=0
即无锁,并使用CAS将state改为1,并将当前线程设为exclusiveOwnerThread,即占有锁;
②如果state>0
,则判断当前线程是否等于exclusiveOwnerThread,如果是即锁重入,就让state值++;
2.2 解锁(state自减)
1.调用 tryRelease(1) 方法,使state减1,
2.判断 如果减1之后state为0,则将exclusiveOwnerThread设为null,即释放锁,并返回true;
如果state减1之后不为0,则返回fasle;
例:在主线程调用m1进行加锁,m1中调用m2进行加锁;
打印日志:
3. 可中断机制(跳出阻塞)
加锁时执行的是 reentrantlock对象.
lockInterrupitly()
方法,而不是lock()
方法!
执行 线程.
interrupt()
方法时就会被中断;
【不中断的模式】下,即使它被打断,会仍然驻留在AQS的等待队列中被阻塞;
【可打断模式】下,如果没有获得锁,进入 doAcquireInterruptibly() 方法而不是acquireQueued(),在线程被park阻塞的状态时,如果被interrupt
则会抛出异常,线程就不会在AQS中的等待队列继续等待;
例:
结果:
4. 公平锁(加锁时 看排队位置)
reentrantlock默认也是不公平,但是可以设置为公平锁;参数可以设置boolean,true即公平;
公平锁即先入先得 !
公平锁会降低并发度;
非公平锁:
加锁时,谁最先调用nonFairAcquire(),如果state=0,则直接用CAS机制将state改为1,设置Owner线程,不去检查AQS的等待队列;
公平锁:
1.最终调用 tryAcqure() 方法,会用hasQueuePredecessors()方法检查当前线程在AQS等待队列中的位置;
2.hasQueuePredecessors即 检查当前这个线程在AQS队列中的位置是否处于head.next
(最优先的位置,head是哨兵节点不关联线程),
如果线程是 就使用CAS机制将state
设为1,并设置当前线程为exclusiveOwnerThread
,
不是就不会往下执行,不会用当前线程占有锁;
5. 锁超时
通过设置超时时间来避免线程无限制等待下去,以防止死锁;
reentrantlock.trylock(超时时间)
会返回布尔类型,在超时时间内都去尝试获取锁;
reentrantlock.trylock()
不带参数即尝试获取锁,获取不到锁立刻结束等待;
例:
此时主线程一直占有锁,t1尝试获取锁,等待了1秒,再返回false;
6. 条件变量
ConditionObject条件变量的等待队列 类似Moniter的waitset队列 !
每个条件变量其实就对应着一个等待队列(双向链表),实现类是AQS中的ConditionObject;
6.1 await() 加入条件变量队列+fullRelease
前提:线程占有锁,state=1
,当前线程-0是Owner线程
即占用着锁,调用 await()
:
- 执行await()方法,进入addConditionWaiter()方法,会 创建Node节点关联当前线程-0,并将节点加入到 ConditionObject条件变量的队列 中去,然后将节点的waitStatus状态设为 -2 ,并且执行
park()
阻塞该线程;
(-2在条件变量里是等待的状态);
(如果队列为空,就将节点作为firstNode,如果不为空就加到队列的尾部); - 执行 fullyRelease():然后将当前这个线程占用的锁都 释放 掉,因为可能有锁重入;
- 既然是释放锁,就会在同步器的阻塞队列中head的后继节点 唤醒,即
unparkSuccesser()
,后面的线程就能去竞争锁;
6.2 singal()
只有Owner线程
才有资格唤醒条件变量中的线程;
假设Thread-1 唤醒Thread-0:
- 先 判断 当前线程是不是锁的持有者,如果不是则报错;
- 获取ConditionObject条件变量队列的头元素firstWaiter,如果不为空,就调用doSignale()将其 移除 等待队列,
然后再将这个节点 转移 到同步器的阻塞队列中并将waitStatues改为0
(因为阻塞队列最后一个节点的waitStatues是0), 这样线程就有机会抢占锁了;
如果遇到中断等,就取消转移,将等待队列的下一个节点来唤醒;
synchronized和ReentrantLock的区别 ?
ReentrantLock底层基于AQS
实现,用volatile修饰的state
属性和阻塞队列
来实现线程的同步执行,从而达到线程安全性的目的;
ReentrantLock是可重入的互斥锁,虽然具有与synchronized相同功能,但是会比synchronized更加灵活(有更多的方法),能解决synchronized关键字在一些并发场景下不适用的问题;
-
synchronized 属于
JDK
层面的, 而 ReentrantLock 依赖于juc并发包
的API; -
ReentrantLock可以指定是
公平锁
还是非公平锁。⽽synchronized只能是非公平锁
; -
可以设置
中断
:synchronized只能等待同步代码块执行结束,不可以中断,而reentrantlock可以调用线程的interrupt方法来中断等待; -
可以设置
超时时间
:synchronized一旦阻塞会进入EntryList,一直等待不会放弃;
而ReentrantLock可以设置一个超时时间,超过一定时间可以放弃争抢锁; -
可以支持多个变量:类似于调用wait方法时,不满足条件的线程进入waitset队列等待CPU随机调度,支持多个变量表示支持多个类似自定义waitset,这样就可以指定对象来唤醒了。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)