摘要: 原创出处 http://cmsblogs.com/?p=2197 「小明哥」欢迎转载,保留摘要,谢谢!
在前面提到过,AQS 是构建 Java 同步组件的基础,我们期待它能够成为实现大部分同步需求的基础。
AQS 的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态。对于子类而言,它并没有太多的活要做,AQS 已经提供了大量的模板方法来实现同步,主要是分为三类:
独占式获取和释放同步状态
共享式获取和释放同步状态
查询同步队列中的等待线程情况。
自定义子类使用 AQS 提供的模板方法,就可以实现自己的同步语义。
独占式,同一时刻,仅有一个线程持有同步状态。
#acquire(int arg)
方法,为 AQS 提供的模板方法。该方法为独占式获取同步状态,但是该方法对中断不敏感。也就是说,由于线程获取同步状态失败而加入到 CLH 同步队列中,后续对该线程进行中断操作时,线程不会从 CLH 同步队列中移除。代码如下
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
第 2 行:调用 #tryAcquire(int arg)
方法,去尝试获取同步状态,获取成功则设置锁状态并返回 true ,否则获取失败,返回 false 。若获取成功,#acquire(int arg)
方法,直接返回,不用线程阻塞,自旋直到获得同步状态成功。
#tryAcquire(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。代码如下:
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
直接抛出 UnsupportedOperationException 异常。
第 3 行:如果 #tryAcquire(int arg)
方法返回 false ,即获取同步状态失败,则调用 #addWaiter(Node mode)
方法,将当前线程加入到 CLH 同步队列尾部。并且, mode
方法参数为 Node.EXCLUSIVE
,表示独占模式。
第 3 行:调用 boolean #acquireQueued(Node node, int arg)
方法,自旋直到获得同步状态成功。详细解析,见 「1.1.1 acquireQueued」 中。另外,该方法的返回值类型为 boolean
,当返回 true 时,表示在这个过程中,发生过线程中断。但是呢,这个方法又会清理线程中断的标识,所以在种情况下,需要调用【第 4 行】的 #selfInterrupt() 方法,恢复线程中断的标识,代码如下:
static void selfInterrupt() { Thread.currentThread().interrupt(); }
boolean #acquireQueued(Node node, int arg)
方法,为一个自旋的过程,也就是说,当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出,否则会一直执行下去。
流程图如下:
代码如下:
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); p.next = null; // help GC failed = false; return interrupted; } // 获取失败,线程等待--具体后面介绍 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { // 获取同步状态发生异常,取消获取。 if (failed) cancelAcquire(node); } }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获得前一个节点的等待状态 int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
Node.SIGNAL
时,表示 pred
的下一个节点 node
的线程需要阻塞等待。在 pred
的线程释放同步状态时,会对 node
的线程进行唤醒通知。所以,【第 9 行】返回 true ,表明当前线程可以被 park,安全的阻塞等待。