目录
参考
AQS: https://blog.youkuaiyun.com/anlian523/article/details/106752414
AQS 在 ThreadPoolExecutor中的应用;
“何时出队列?”和“如何出队列?
下面我们从“何时出队列?”和“如何出队列?
”两个方向来分析一下acquireQueued源码:
acquireQueued()方法解释了 node获取锁失败之后, “何时被中断”、中断之后又是“何时出队列”,和“如何出队列” ;
关键字:
for (;;) + waitStatus + LockSupport.park(this);
线程获取锁失败之后, 通过
addWaiter(Node.EXCLUSIVE), arg)
方法把当前线程封装成Node追加到等待队列(双向链表);
再调
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法,该方法的入参正是当前线程构建的Node node
我们可以看到,
以自旋的方式做了下面步骤:
先找node的前驱节点,如果前驱节点是head头结点, 说明此事node排在“队首”, 是第一个有效节点,则去争抢锁;
不然的话,调用
shouldParkAfterFailedAcquire(p, node)
方法判断是否需要中断当前线程, 如果前驱节点的 waitStatus = -1 , SIGNAL, 则需要中断当前线程;
如果前驱节点的 waitStatus > 0,即 CANCELLED的, 则自旋跳过所有的 CANCELLED节点,并将node重新指向;
因为上层方法通过for(;;)自旋,这里最后会将前驱节点的waitStatus置位-1,即SIGNAL, 然后返回true,告知可以中断当前线程了;
之后调了
parkAndCheckInterrupt()方法去中断当前线程;
LockSupport.park(this); 中断线程; 线程此刻被中断,直到 通过upPark()方法唤醒; ===》 什么时候唤醒呢?
被唤醒了之后, 会继续执行以下方法 Thread.interrupted() 用于查询当前线程的中断标识状态,并重置中断标识状态为false; 如果不重置中断标识状态,当为true时, 假如唤醒之后仍然获取锁失败, 此时还需要再调LockSupport.park(this)方法中断, 但是此时中断标识状态为true的话,将不能中断线程; 所以通过
Thread.interrupted()重置中断标识状态为false;
https://cgiirw.github.io/2018/05/27/Interrupt_Ques/
通过for(;;) 自旋,最终会不停尝试获取锁,获取失败就中断等待,当被唤醒之后再次自旋获取锁,周而复始, 直到获取锁成功;
如果中间抛了异常,会执行 finally里的 cancelAcquire(node); 方法;
自旋, 先判断首否首第一个有效节点,再获取锁,获取失败时, 判断首否需要中断,
释放锁:
释放锁成功之后, 会用头结点作为参数,唤醒队列中的后继节点
我们发现,只有h != null && h.waitStatus !=0 的时候才会唤醒,换句话说,
没有头结点时说明没有等待线程,
有头结点,且头节点的waitStatus == 0时,也表示没有等待线程节点,
头节点的waitStatus != 0时, 表示有等待线程节点;
ReentrantLock
公平模式和非公平模式对lock()接口的实现不同点:
非公平模式锁会首先通过CAS争抢锁;
何时初始化队列 、如何入队?
获取不到锁时,需要追加到队列尾部,当没有队列时,需要初始化,
tail == null时, 表示没有队列;
通过CAS,即
compareAndSetHead(new Node()
方法,控制只会生成一个队列,而不会因为并发导致生成另个队列;
之后将head和tail都指向这个"虚节点"(里面不存放线程,故叫“虚”节点);
再通过自旋的方式,把封装有线程的node追加到队列里;
如果一个线程未获取到锁, 什么情况下会挂起(或暂停)该线程呢?
如果一个线程未获取到锁,会自旋不停的判断前驱节点是不是头结点,如果是头节点而且能抢占锁成功,则获取锁成功,将头结点(即node的前驱节点释放掉GC),并将head指向自己;
如果前驱不是头结点,或争抢锁失败,则需要判断 前驱节点的waitStatus 是否是SIGNAL,只有当 waitStatus = SIGNAL时,才会尝试挂起node的线程;
通过
parkAndCheckInterrupt() 方法挂起线程
挂起后,线程状态变成 WAITING,(参考线程的几种状态: 就绪态、等待态等 ,参考: ), 它需要等待一个unpark() 或中断 interrupt();