ReentrantLock之lock加锁与unlock释放锁
2021-05-18 21:51:15 2 举报
根据周洋老师所讲AQS课程,总结出AQS执行流程
作者其他创作
大纲/内容
Thread = nullwaitStatus = 0傀儡节点,哨兵节点(占位)
true
aqs队列(链表)
没有其他线程在排队,调用enq构造队列,并将node加入队列。
这个队列表示,在节点n2插入队列之前,没有其他节点在排队。注意,当前持有锁的线程,永远不会处于排队队列中。这是因为:当一个线程调用lock获取锁时,只有两种情况:1)锁处于自由状态(state==0),且它不需要排队,通过cas立刻获取到了锁,这种情况,显然它不会处于排队队列中;2)锁处于非自由状态,线程加入到了排队队列的队头(不包括head)等待锁。将来某一时刻,锁被其他线程释放,并唤醒这个排队的线程,线程唤醒后。执行tryAcquire.获取到了锁,然后重新维护排队队列,将自己从队列移出(acquireQueued方法)。所以,不管哪种情况,持有锁的线程,永远不会处于排队列表中。
返回node
false(即==0)
sync.lock()
pred.waitStatus==Node.SIGNAL(-1)?
Node:n2thrad = t2prev = n1next = n3
设置pred.waitStatus==-1
unparkSuccessor(head)
tryRelease(1)
这个队列表示,在线程C进入之前,已经有一个线程在排队获取锁了。
Node:n1thrad = nullprev = nullnext = n2
enq方法
false
公平锁
public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
1.计算变量c为当前state状态-1。若锁即将被释放,则c为0。2.判断当前线程是否是锁的独占者,如果不是则抛出异常3.若c等于0,标志位free则设定为true,把当前独占线程置为空。4.设置锁的状态为c5.返回free标志位,即锁已经释放成功返回true,否则返回false
这里面的公平与非公平差别:1.公平是当前同步队列中没有前驱节点(也就是没有线程在等待)时才会去cas修改state状态,即根据线程发出请求的顺序获取锁。2.非公平是刚进入lock方法时会直接cas去尝试获取锁,不成功才会去acquire。并且在nofairTryAcquire时也没有判断是否有前驱节点在等待,直接cas尝试获取锁。默认是非公平锁优点:有更好的性能,因为它的吞吐量比较大。缺点:让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
返回false
阻塞结束
tail
返回true,表示释放锁成功,走接下来的流程
窗口每次只能服务一个顾客,初始没人
next
parkAndCheckInterrupt线程进入阻塞状态
Thread = nullwaitStatus = -1哨兵节点
返回false,表示当前线程的节点还无法从队列中剔除,从而无法解除阻塞等待状态
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
public void lock() { sync.lock(); }
返回true
acquire(1)
返回true,表示队列head的哨兵节点不为空且waitStatus为-1,即将为当前线程抢占锁做准备,为当前线程的节点解除阻塞状态并从队列中剔除
abstract void lock()
prev
1
tryAcquire
银行受理业务窗口Thread = null
p==head?表示查询node是不是第一个排队的
返回false,表示线程没有获取到锁
整个ReentrantLock 的加锁过程,可以分为三个阶段:1、尝试加锁;2、加锁失败,线程入队列;3、线程入队列后,进入阻赛状态。对应下面①②③三部分。
Thread = ThreadCwaitStatus = 0顾客C
false再次刷新
sync.release(1)
CAS获取锁
执行排队
直接acqure锁
执行park逻辑
B/C...
FairSync里lock()
addWaiter
下面虚线是模拟银行的等候区,每个线程就是一个顾客
Node h = head; if (h != null && h.waitStatus != 0)
lock
public void unlock() { sync.release(1); }
2
state = 0
再次循环判断
pred != null
acquireQueued返回,表示线程获取到锁,线程逐级返回,加锁过程结束。
head
shouldParkAfterFailedAcquire方法详解
Node:n3thrad = t3prev = t2next = null
Thread = ThreadBwaitStatus = -1顾客B
Node:n2thrad = t2prev = n1next = null
返回true,表示获取到了锁,把当前线程设置为锁的独占者
false,执行上图步骤2
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
这里步骤2中先把node设置为头结点,并且把前一个节点即原哨兵节点的next引用置为空。等后面shouldParkAfterFailedAcquire方法里把node节点置为新的哨兵节点并且把新哨兵节点指向原哨兵节点的前驱指针置为空,然后把head指针指向新的哨兵节点;这时候node节点在队列里真正不再排队了,一段时间gc就会发现原哨兵节点没有被其他对象引用了就会把该节点回收掉。
final void lock() { acquire(1); }
公平锁与非公锁相似,唯一的不同则是tryAcquire时需要多一个判断条件hasQueuedPredecessors,即判断队列中不为空并且head哨兵节点的下一个节点不为空或者说下一个节点为空但是这个节点的线程与当前线程不相同;简单来说就是判断是否有线程在等待。
表示获取到锁:1. node设置为头结点2、释放之前的头结点这个逻辑执行完成后,node节点就不再排队了(获取到锁的线程,不会在排队队列中了)
unlock
AQS = state + CLH队列
state = 1
非公平锁
返回false,表示线程没有释放成功锁
计算head哨兵节点的waitstaus小于0(这里即为-1)时,就把哨兵节点的waitstatus设置为0。获取哨兵节点的下一个节点s(即当前将要抢占锁的线程);判断是否为空,不为空则unpark这个节点,若为空或者这个节点的waitStatus大于0(即waitStatus为1是CANCELLED取消状态),走接下来的步骤。1.如果条件是s节点为空,从队列的尾节点开始往前遍历,直到节点为空或者节点等于s节点(即遍历尾节点到s节点之间的节点,当碰到空节点就结束循环)。判断这些节点的waitStatus是否小于等于0(即非取消状态),把s节点置满足条件的节点。遍历下来就是把s节点设置为s节点之后从尾节点到s节点之间的第一个不为取消状态的节点或者设置为s节点之后从尾节点到尾节点之前的第一个空节点之间的第一个不为取消状态的节点,所以就是设置好之后继续把新设置的s节点unpark掉。2.如果条件是s节点为取消状态,先把这个s节点置为空,然后从队列的尾节点开始往前遍历。判断这些节点是否满足条件,把s节点置为满足条件的节点。然后把设置好之后的新s节点unpark掉。
三个顾客来银行办理业务,类似三个线程ThreadAThreadBThreadC
pred.waitStatus>0?
返回true,表示获取到了锁,线程逐级返回,加锁过程结束
addWaiter详解
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
队列已经初始化,将node加入到队列末尾,加入后队列可能如下
Sync继承了AbstractQueuedSynchronizer
NonfairSync里lock()
3
acquireQueued
ReentrantLock
0 条评论
下一页