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


private final boolean parkAndCheckInterrupt() {// 阻塞线程LockSupport.park(this);// 返回线程是否中断过 , 并且清除中断状态(在获得锁后会补一次中断)return Thread.interrupted();}这里的阻塞线程很容易理解 , 但为啥要判断线程是否中断过呢 , 因为如果线程在阻塞期间收到了中断 , 唤醒(转为运行态)获取锁后(acquireQueued 为 true)需要补一个中断 , 如下所示:
public final void acquire(int arg) {if (!tryAcquire(arg) }至此 , 获取锁的流程已经分析完毕 , 不过还有一个疑惑我们还没解开:前文不是说 Node 状态为取消状态会被取消吗 , 那 Node 什么时候会被设置为取消状态呢 。
回头看 acquireQueued
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {// 省略自旋获取锁代码} finally {if (failed)// 如果线程自旋中因为异常等原因获取锁最终失败 , 则调用此方法cancelAcquire(node);}}看最后一个 cancelAcquire 方法 , 如果线程自旋中因为异常等原因获取锁最终失败 , 则会调用此方法
private void cancelAcquire(Node node) {// 如果节点为空 , 直接返回if (node == null)return;// 由于线程要被取消了 , 所以将 thread 线程清掉node.thread = null;// 下面这步表示将 node 的 pre 指向之前第一个非取消状态的结点(即跳过所有取消状态的结点),waitStatus > 0 表示当前结点状态为取消状态Node pred = node.prev;while (pred.waitStatus > 0)node.prev = pred = pred.prev;// 获取经过过滤后的 pre 的 next 结点 , 这一步主要用在后面的 CAS 设置 pre 的 next 节点上Node predNext = pred.next;// 将当前结点设置为取消状态node.waitStatus = Node.CANCELLED;// 如果当前取消结点为尾结点 , 使用 CAS 则将尾结点设置为其前驱节点 , 如果设置成功 , 则尾结点的 next 指针设置为空if (node == tail} else {// 这一步看得有点绕 , 我们想想 , 如果当前节点取消了 , 那是不是要把当前节点的前驱节点指向当前节点的后继节点 , 但是我们之前也说了 , 要唤醒或阻塞结点 , 须在其前驱节点的状态为 SIGNAL 的条件才能操作 , 所以在设置 pre 的 next 节点时要保证 pre 结点的状态为 SIGNAL , 想通了这一点相信你不难理解以下代码 。int ws;if (pred != headif (next != null} else {// 如果 pre 为 head , 或者pre 的状态设置 SIGNAL 失败 , 则直接唤醒后继结点去竞争锁 , 之前我们说过 ,SIGNAL 的结点取消(或释放锁)后可以唤醒后继结点unparkSuccessor(node);}node.next = node; // help GC}}这一段代码有点绕 , 我们一个个来看 , 首先考虑以下情况
1、首先第一步当前节点之前有取消结点时 , 则逻辑如下
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
2、如果当前结点既非头结点的后继结点 , 也非尾结点 , 即步骤 1 所示 , 则最终结果如下
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
这里肯定有人问 , 这种情况下当前节点与它的前驱结点无法被 GC 啊 , 还记得我们上文分析锁自旋时的处理吗,再看下以下代码
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;// 省略无关代码if (ws > 0) {// 移除取消状态的结点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;}return false;}这段代码会将 node 的 pre 指向之前 waitStatus 为非 CANCEL 的节点