万字超强图文讲解AQS以及ReentrantLock应用(建议收藏) (5)

哨兵,顾名思义,是用来解决国家之间边界问题的,不直接参与生产活动。同样,计算机科学中提到的哨兵,也用来解决边界问题,如果没有边界,指定环节,按照同样算法可能会在边界处发生异常,比如要继续向下分析的 acquireQueued() 方法

final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // "死循环",尝试获取锁,或者挂起 for (;;) { // 获取当前节点的前驱节点 final Node p = node.predecessor(); // 只有当前节点的前驱节点是头节点,才会尝试获取锁 // 看到这你应该理解添加哨兵节点的含义了吧 if (p == head && tryAcquire(arg)) { // 获取同步状态成功,将自己设置为头 setHead(node); // 将哨兵节点的后继节点置为空,方便GC p.next = null; // help GC failed = false; // 返回中断标识 return interrupted; } // 当前节点的前驱节点不是头节点 //【或者】当前节点的前驱节点是头节点但获取同步状态失败 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

获取同步状态成功会返回可以理解了,但是如果失败就会一直陷入到“死循环”中浪费资源吗?很显然不是,shouldParkAfterFailedAcquire(p, node) 和 parkAndCheckInterrupt() 就会将线程获取同步状态失败的线程挂起,我们继续向下看

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前驱节点的状态 int ws = pred.waitStatus; // 如果是 SIGNAL 状态,即等待被占用的资源释放,直接返回 true // 准备继续调用 parkAndCheckInterrupt 方法 if (ws == Node.SIGNAL) return true; // ws 大于0说明是CANCELLED状态, if (ws > 0) { // 循环判断前驱节点的前驱节点是否也为CANCELLED状态,忽略该状态的节点,重新连接队列 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 将当前节点的前驱节点设置为设置为 SIGNAL 状态,用于后续唤醒操作 // 程序第一次执行到这返回为false,还会进行外层第二次循环,最终从代码第7行返回 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }

到这里你也许有个问题:

这个地方设置前驱节点为 SIGNAL 状态到底有什么作用?

保留这个问题,我们陆续揭晓

如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true ,程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起

private final boolean parkAndCheckInterrupt() { // 线程挂起,程序不会继续向下执行 LockSupport.park(this); // 根据 park 方法 API描述,程序在下述三种情况会继续向下执行 // 1. 被 unpark // 2. 被中断(interrupt) // 3. 其他不合逻辑的返回才会继续向下执行 // 因上述三种情况程序执行至此,返回当前线程的中断状态,并清空中断状态 // 如果由于被中断,该方法会返回 true return Thread.interrupted(); }

被唤醒的程序会继续执行 acquireQueued 方法里的循环,如果获取同步状态成功,则会返回 interrupted = true 的结果

程序继续向调用栈上层返回,最终回到 AQS 的模版方法 acquire

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

你也许会有疑惑:

程序已经成功获取到同步状态并返回了,怎么会有个自我中断呢?

static void selfInterrupt() { Thread.currentThread().interrupt(); }

万字超强图文讲解AQS以及ReentrantLock应用(建议收藏)

如果你不能理解中断,强烈建议你回看 Java多线程中断机制

到这里关于获取同步状态我们还遗漏了一条线,acquireQueued 的 finally 代码块如果你仔细看你也许马上就会有疑惑:

到底什么情况才会执行 if(failed) 里面的代码 ?

if (failed) cancelAcquire(node);

这段代码被执行的条件是 failed 为 true,正常情况下,如果跳出循环,failed 的值为false,如果不能跳出循环貌似怎么也不能执行到这里,所以只有不正常的情况才会执行到这里,也就是会发生异常,才会执行到此处

查看 try 代码块,只有两个方法会抛出异常:

node.processor() 方法

自己重写的 tryAcquire() 方法

先看前者:

万字超强图文讲解AQS以及ReentrantLock应用(建议收藏)

很显然,这里抛出的异常不是重点,那就以 ReentrantLock 重写的 tryAcquire() 方法为例

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfypd.html