1.5w字,30图带你彻底掌握 AQS!(建议收藏)( 六 )

通过状态的定义 , 我们可以猜测一下 AQS 对线程自旋的处理:如果当前节点的上一个节点不为 head , 且它的状态为 SIGNAL , 则结点进入阻塞状态 。 来看下代码以映证我们的猜测:
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();// 如果前一个节点是 head , 则尝试自旋获取锁if (p == headp.next = null; // help GCfailed = false;return interrupted;}// 如果前一个节点不是 head 或者竞争锁失败 , 则进入阻塞状态if (shouldParkAfterFailedAcquire(p, node)}} finally {if (failed)// 如果线程自旋中因为异常等原因获取锁最终失败 , 则调用此方法cancelAcquire(node);}}先来看第一种情况 , 如果当前结点的前一个节点是 head 结点 , 且获取锁(tryAcquire)成功的处理
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
可以看到主要的处理就是把 head 指向当前节点 , 并且让原 head 结点出队 , 这样由于原 head 不可达 ,会被垃圾回收 。
注意其中 setHead 的处理
private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}将 head 设置成当前结点后 , 要把节点的 thread, pre 设置成 null , 因为之前分析过了 , head 是虚节点 , 不保存除 waitStatus(结点状态)的其他信息 , 所以这里把 thread ,pre 置为空 , 因为占有锁的线程由 exclusiveThread 记录了 , 如果 head 再记录 thread 不仅多此一举 , 反而在释放锁的时候要多操作一遍 head 的 thread 释放 。
如果前一个节点不是 head 或者竞争锁失败 , 则首先调用 shouldParkAfterFailedAcquire 方法判断锁是否应该停止自旋进入阻塞状态:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)// 1. 如果前置顶点的状态为 SIGNAL , 表示当前节点可以阻塞了return true;if (ws > 0) {// 2. 移除取消状态的结点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 3. 如果前置节点的 ws 不为 0 , 则其设置为 SIGNAL ,compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}这一段代码有点绕 , 需要稍微动点脑子 , 按以上步骤一步步来看
1、 首先我们要明白 , 根据之前 Node 类的注释 , 如果前驱节点为 SIGNAL , 则当前节点可以进入阻塞状态 。
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
如图示:T2 , T3 的前驱节点的 waitStatus 都为 SIGNAL , 所以 T2 , T3 此时都可以阻塞 。
2、如果前驱节点为取消状态 , 则前驱节点需要移除 , 这些采用了一个更巧妙的方法 , 把所有当前节点之前所有 waitStatus 为取消状态的节点全部移除 , 假设有四个线程如下 , T2 , T3 为取消状态 , 则执行逻辑后如下图所示 , T2, T3 节点会被 GC 。
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
3、如果前驱节点小于等于 0 , 则需要首先将其前驱节点置为 SIGNAL,因为前文我们分析过 , 当前节点进入阻塞的一个条件是前驱节点必须为 SIGNAL , 这样下一次自旋后发现前驱节点为 SIGNAL , 就会返回 true(即步骤 1)
shouldParkAfterFailedAcquire 返回 true 代表线程可以进入阻塞中断 , 那么下一步 parkAndCheckInterrupt 就该让线程阻塞了