实际上,Condition的所有await()方法变体都调用addConditionWaiter()添加阻塞线程到条件队列中。我们按照分析同步等待队列的情况,分析一下条件等待队列。正常情况下,假设有2个线程thread-1和thread-2进入条件等待队列,都处于阻塞状态。
先是thread-1进入条件队列:
然后是thread-2进入条件队列:
条件等待队列看起来也并不复杂,但是它并不是单独存在和使用的,一般依赖于同步等待队列,下面的一节分析Condition的实现的时候再详细分析。
独占模式与共享模式前文提及到,同步器涉及到独占模型和共享模式。下面就针对这两种模式详细分析一下AQS的具体实现源码。
独占模式AQS同步器如果使用独占(EXCLUSIVE)模式,那么意味着同一个时刻,只有唯一的一个节点所在线程获取(acuqire)原子状态status成功,此时该线程可以从阻塞状态解除继续运行,而同步等待队列中的其他节点持有的线程依然处于阻塞状态。独占模式同步器的功能主要由下面的四个方法提供:
acquire(int arg):申请获取arg个原子状态status(申请成功可以简单理解为status = status - arg)。
acquireInterruptibly(int arg):申请获取arg个原子状态status,响应线程中断。
tryAcquireNanos(int arg, long nanosTimeout):申请获取arg个原子状态status,带超时的版本。
release(int arg):释放arg个原子状态status(释放成功可以简单理解为status = status + arg)。
独占模式下,AQS同步器实例初始化时候传入的status值,可以简单理解为"允许申请的资源数量的上限值",下面的acquire类型的方法暂时称为"获取资源",而release方法暂时称为"释放资源"。接着我们分析前面提到的四个方法的源码,先看acquire(int arg):
public final void acquire(int arg) { // 获取资源成功或者新增一个独占类型节点到同步等待队列成功则直接返回,否则中断当前线程 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 此方法必须又子类覆盖,用于决定是否获取资源成功 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 中断当前线程 static void selfInterrupt() { Thread.currentThread().interrupt(); } // 不可中断的独占模式下,同步等待队列中的线程获取资源的方法 final boolean acquireQueued(final Node node, int arg) { boolean interrupted = false; try { for (;;) { // 获取新入队节点的前驱节点 final Node p = node.predecessor(); // 前驱节点为头节点并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquire尝试获取资源,除非阻塞或者跳出循环 if (p == head && tryAcquire(arg)) { // 设置新入队节点为头节点,原来的节点会从队列中断开 setHead(node); p.next = null; // help GC return interrupted; // <== 注意,这个位置是跳出死循环的唯一位置 } // 判断是否需要阻塞当前获取资源失败的节点中持有的线程 if (shouldParkAfterFailedAcquire(p, node)) // 阻塞当前线程,如果被唤醒则返回并清空线程的中断标记 interrupted |= parkAndCheckInterrupt(); } } catch (Throwable t) { cancelAcquire(node); if (interrupted) selfInterrupt(); throw t; } } /** * 检查并且更新获取资源失败的节点的状态,返回值决定线程是否需要被阻塞。 * 这个方法是所有循环获取资源方法中信号控制的主要方法 */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 这里记住ws是当前处理节点的前驱节点的等待状态 int ws = pred.waitStatus; if (ws == Node.SIGNAL) // 前驱节点状态设置成Node.SIGNAL成功,等待被release调用释放,后继节点可以安全地进入阻塞状态 return true; if (ws > 0) { // ws大于0只有一种情况Node.CANCELLED,说明前驱节点已经取消获取资源, // 这个时候会把所有这类型取消的前驱节点移除,找到一个非取消的节点重新通过next引用连接当前节点 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 其他等待状态直接修改前驱节点等待状态为Node.SIGNAL pred.compareAndSetWaitStatus(ws, Node.SIGNAL); } return false; } // 阻塞当前线程,获取并且重置线程的中断标记位 private final boolean parkAndCheckInterrupt() { // 这个就是阻塞线程的实现,依赖Unsafe的API LockSupport.park(this); return Thread.interrupted(); }上面的代码虽然看起来能基本理解,但是最好用图推敲一下"空间上的变化":