Java ReentreenLock lock 工作流程解读
2021-07-06 21:46:41 6 举报
ReentreenLock源码分析解读
作者其他创作
大纲/内容
分割线
表示获取到锁:1、node设置为头结点2、释放之前的头结点,这个逻辑执行完成后,node节点就不再需要排队了(时刻谨记: 获取到锁的线程,不会在排队队列中了)
true
parkAndCheckInterrupt线程进入阻塞状态
acquireQueued
场景: 银行窗口办理业务(假设只有一个窗口),先后来了三个顾客。PS: 看下面源码解析的流程时,代入此场景。ThreadAThreadBThreadC
lock
sync.lock()
返回node
两种情况,会导致阻塞结束:1. 持有锁的线程,释放锁后,将这个线程unpark了。此时该线程,一定排在队列的队头(不包括head节点)2. 线程被interrupt了。(注意:在外部interrupt这个线程,不是抛出InterruptionException,这一点和sleep、wait阻塞不一样)
公平锁
返回true,执行parkAndCheckInterrupt
返回false
false
enq方法
队列尚未初始化,调用enq方法该方法生成一个空的Node对象(new Node(),右图粉色部分),插入到AQS的头部,然后将参数node,作为其后继节点,插入队列,方法执行完毕,队列如右图
addWaiter详解
addWaiter
返回true,表示获取到锁了,线程逐级返回,加锁过程结束
队列已初始化,将node加入到队列的末尾,加入后队列可能如下
成功获得锁,设置锁的持有线程为当前线程,返回
没有其他的线程在排队,条用enq方法构建队列,并将node加入到队列
flase
再次循环判断
abstract void lock()
>0,只能 == 1,即CANCELLED,表示node的前辈节点,已经取消了,此时需要将链表关系重新维护下,即将其前辈节点从队列中移出
acquireQueued返回,表示线程获取到锁,线程逐级返回,加锁过程结束
非公平锁
false 再次循环判断
整个AQS的核心和难点之一。注意方法使用了for(;;),不断自旋。首先判断node的前辈节点,是不是head,如果是,说明他是下个可以获得锁的线程,则调用一次tryAcquire,尝试获取锁,若获取到,则将链表关系重新维护下(node设置为head,之前的head从链表中移出),然后返回。如果node的前辈节点不是head,或者获取锁失败,再判断其前辈节点的waitStatus,是不是SINGAL,如果是,则当前线程调用park(),进入阻塞状态。如不是:1. waitStatus== 0(Node的默认值),则设置为SINGAL2. waitStatus> 0 (==1),则表示其前辈节点已经被取消,将取消的节点从队列移出,重新维护下排队链表的关系。然后再次进入for循环,上面的逻辑重新执行一遍。ps: 和doAcquireInterruptibly相比,二者的主要区别是,发现线程被中断之后的处理逻辑
Sync继承AbstractQueuedSynchronizer
ReentrantLock
NonfairSync#lock()
tryAcquire
FairSync#lock()
pred.waitStatus > 0 ?
阻塞结束
注意: 这里是设置pred节点,而不是node节点的waitStatus。 -1 表示节点线程处于阻塞状态了问题: 为何每个线程进入该方法后,修改的是上一个节点的waitStatus,而不是自己修改自己的?因为线程调用park后,无法设置自己的这个状态,若在调用park之前设置,存在不一致的问题。所以,没个node的waiteStatus,在其后继节点加入时设置
acquire(1)
设置
acquireQueued方法详解
执行park()逻辑
CAS获取锁
pred.waitStatus == Node.SIGNAL(-1) ?
这部分,是node加入队列后的处理逻辑,这里会有一次自旋,尝试获取锁,获取不到,才调用park,阻塞自己
pred != null
p == head
将当前线程封装成Node对象,并加入排队队列中。根据排队队列是否执行过初始化,执行1、2不同处理逻辑。1:表示当前队列不为空,即之前已经初始化过了,此时,只需要将node加入排队队列的队尾即可。2:表示当前队列为空,需要进行队列初始化工作,enq会初始化一个空的Node,作为队列的head,然后将需要排队的线程作为head的next节点插入
此方法接收两个Node对象参数: 参数2 是准备执行park操作的节点node,参数1是其前辈节点pred
0 条评论
下一页