这里主要记录一下ReentrantLock的工作流程,以稍微复杂一些的非公平锁展开
主要类架构图
AbstractQueuedSynchronizer类底层的数据结构是使用CLH(Craig,Landin,and Hagersten)队列
是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。
主要字段介绍,之后会多次提到
Node中的重要字段
waitStatus,节点的等待状态,下面为状态枚举
CANCELLED
,值为1,表示当前的线程被取消。
SIGNAL
,值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。
CONDITION
,值为-2,表示当前节点在等待condition,也就是在condition queue中。
PROPAGATE
,值为-3,表示当前场景下后续的acquireShared能够得以执行。值为0,新建一个Node的默认值,表示当前节点在sync queue中,等待着获取锁。
AbstractQueuedSynchronizer中的重要字段
- head指针指向CLH队列头结点
(CLH队列拥有一个dummyNode)
- tail指针指向CLH队列的尾节点
- state:AQS同步状态,在独占模式下,1代表已经有线程占用,0代表无线程占用
ReentrantLock加锁
场景:线程A、B、C竞争同一把锁,线程A先获得锁,并执行业务代码,持续一个很长的时间,这时B、C按顺序来竞争这把锁。
-
线程A获取锁
此时AQS的state还为默认值0,CAS将state的值更改为1,设置独占线程为线程A。
-
线程B来获取锁
当前state的值已经为1了,所以走acquire(1)
这部分是核心代码块
-
首先是tryAcquire()方法,这时AQS中模版模式的运用,具体由该抽象类的子类实现。
state为1,当前的独占线程是A而不是B,所有返回false。
-
此时走到addWatier方法,参数为一个独占模式的Node节点
tail指针当前还是null,所以进入到enq方法
这里是一个死循环
第一次循环,t为null,CAS更新head指针指向一个dummyNode(虚拟节点),tail指针也指向这个地址。
第二次循环,t指向dummyNode,非空,将Node(B)设置为该dummyNode的后继节点,退出循环。
-
走到acquireQueued方法,参数为上述加入到CLH的独占模式节点Node(B)和1。
进入到死循环
第一次循环,p指向Node(B)的前驱节点,即dummyNode,head指向dummyNode,进入到tryAcquire方法。
同上,因为state为1且当前独占线程为A,所以返回false。
进入到shouldParkAfterFailedAcquire方法,参数为dummyNode和当前节点Node(B)
后面会发现,都是后继节点将前驱节点的waitStatus更新为-1
ws为0,CAS将dummyNode的watiStatus更新为-1,返回true
进入parkAndCheckInterrupt
线程B挂起
,至此,线程B并没有完全结束,等到线程B被unpark后,将继续执行。
-
-
线程C来获取锁,此时线程A仍未释放锁,线程B已经进入到CLH队列中。这里和B线程获取锁大致雷同,故仅介绍一些不动的代码。
这里说一下tryAcquire方法,若占用锁的线程释放了锁,此时CLH队列中已经有线程被挂起等待了,突然来了一个线程竞争锁,公平锁和非公平锁有不同的表现
非公平锁:突然来的线程可以直接插队。
公平锁:如果CLH队列中有线程被挂起等待,则需要排队。
线程C进入CLH队列,并将Node(B)的waitStatus更新为-1,线程C挂起等待。
ReentrantLock解锁
正常场景
继续使用上述A、B、C线程的场景,此时A线程占有锁在执行业务代码,B、C线程在CLH队列中挂起等待
现在线程A执行完毕,释放锁。
进入release方法,参数为1
将独占线程置空,将state更新为0,返回true
进入该方法体,h指向dummyNode,进入unparkSuccessor方法,释放dummyNode的后继节点,即Node(B)
将dummyNode的waitStatus更新为0,s指向Node(B),执行LockSupport.unpark方法将B线程释放。
书接上回,B线程挂起处。
返回线程B的中断状态并重置线程B的中断状态
再次走这个循环体,p指向dummyNode,进入到tryAcquire方法,参数为1
当前线程为B,state为0,CAS更为state为1,设置当前独占线程为线程B,返回true
进入if代码块,head指针指向Node(B),dummyNode释放,faild置为false,返回线程B中断标记,若为true,则调用interrupt方法将线程中断状态置为true。
至此线程B独占锁,执行业务代码。
之后,线程B释放锁,线程C获取锁,同上,不再赘述。
异常场景
线程在CLH队列中,发生异常,未能走到866行将failed状态置为false,进入到finally代码块执行cancelAcquire方法.
场景:
线程D执行发生异常,进入到cancelAcquire方法,参数为Node(D),Node©已经是取消状态
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
代码执行,Node(D)将持有的线程引用置为null,跳过Node©,Node(D)的prev指针指向Node(B),Node(D)的waitStatus置为1,CAS更新Node(null)(之前的Node(B))的next指针指向Node(E),Node(D)的next指针指向自己,帮助GC,至此节点C、D出队。