AQS-ConditionObject详解

2 阅读7分钟

ConditionObject 是 AQS 框架内部的非静态内部类,它实现了 Condition 接口,为基于 AQS 的独占锁(如 ReentrantLock)提供了条件等待/通知机制。与 Object.wait/notify 相比,它的核心优势是:一把锁可以绑定多个条件队列,实现更精细的线程调度。


一、ConditionObject 的数据结构

ConditionObject 维护一个单向链表作为条件队列,节点复用 AQS 的 Node 类,但使用 nextWaiter 字段连接,且节点状态固定为 Node.CONDITION

public class ConditionObject implements Condition {
    private transient Node firstWaiter;  // 条件队列头
    private transient Node lastWaiter;   // 条件队列尾
}

队列结构示意

firstWaiter                         lastWaiter
     ↓                                  ↓
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│    Node     │ →  │    Node     │ →  │    Node     │
│ waitStatus  │    │ waitStatus  │    │ waitStatus  │
│ =CONDITION  │    │ =CONDITION  │    │ =CONDITION  │
│ nextWaiter  │    │ nextWaiter  │    │ nextWaiter  │
└─────────────┘    └─────────────┘    └─────────────┘

与同步队列的关键区别

特性同步队列 (Sync Queue)条件队列 (Condition Queue)
数据结构双向链表 (prev + next)单向链表 (nextWaiter)
节点状态SIGNALCANCELLED0PROPAGATECONDITION (-2)
入队时机争锁失败时调用 await()
出队时机获取锁成功时调用 signal()转移到同步队列
线程状态阻塞等待锁 (park)阻塞等待条件 (park)

二、await() 方法详解

await() 是条件等待的核心方法。调用前提:当前线程必须已经持有锁(即位于同步队列的 head 位置)。

2.1 完整源码分析

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    
    // ① 将当前线程加入条件队列尾部(状态设为 CONDITION)
    Node node = addConditionWaiter();
    
    // ② 完全释放锁(包括所有重入次数),并记录释放前的 state
    int savedState = fullyRelease(node);
    
    int interruptMode = 0;
    // ③ 自旋:判断节点是否已被转移到同步队列
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);          // 阻塞,等待 signal
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    
    // ④ 被唤醒后,重新在同步队列中竞争锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    
    // ⑤ 清理条件队列中已取消的节点
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    
    // ⑥ 处理中断
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

2.2 关键步骤剖析

addConditionWaiter():加入条件队列

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 如果尾节点状态不是 CONDITION,说明已取消,先清理整个队列
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 创建新节点,状态为 CONDITION(注意:没有指定模式,同步队列模式由后续转移时决定)
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

fullyRelease(Node):完全释放锁

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        // 调用 release(savedState) 释放所有重入次数,并唤醒同步队列的后继
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

注意:这里释放的是所有重入次数,因为 await() 必须完全释放锁才能让其他线程进入。savedState 用于后续重新获取锁时恢复重入计数。

isOnSyncQueue(Node):判断节点是否已转移

final boolean isOnSyncQueue(Node node) {
    // 状态为 CONDITION 或 prev 为 null,说明肯定不在同步队列
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果有后继节点,说明一定在同步队列(同步队列是双向链表)
    if (node.next != null)
        return true;
    // 否则从 tail 向前遍历查找
    return findNodeFromTail(node);
}

checkInterruptWhileWaiting(Node):处理等待期间的中断

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
  • THROW_IE:中断发生在 signal 之前,后续抛出 InterruptedException
  • REINTERRUPT:中断发生在 signal 之后,仅重新设置中断状态。

acquireQueued(Node, savedState):重新竞争锁

节点被转移到同步队列后,调用此方法在同步队列中自旋/阻塞,直到重新获取到锁,并恢复重入计数(savedState)。

2.3 流程图

2.3.1 一个节点在两个队列间的旅程

sequenceDiagram
    participant T_A as 线程A (等待条件)
    participant T_B as 线程B (发出信号)
    participant Sync as 同步队列
    participant Cond as 条件队列

    rect rgb(255, 250, 240)
        Note over T_A,Sync: 阶段一:await() - 从同步队列转移到条件队列
        T_A->>Sync: 持有锁,位于 head
        T_A->>Cond: 创建节点入条件队列
        T_A->>Sync: 完全释放锁
        Sync->>Sync: 唤醒后继节点
        T_A->>T_A: park() 阻塞在条件队列
    end

    rect rgb(240, 255, 240)
        Note over T_B,Sync: 阶段二:signal() - 从条件队列转移回同步队列
        T_B->>Sync: 持有锁(可能是线程A释放后被T_B获取)
        T_B->>Cond: doSignal()
        Cond->>Cond: 摘除头节点
        Cond->>Sync: transferForSignal() 节点入同步队列尾部
    end

    rect rgb(240, 248, 255)
        Note over T_A,Sync: 阶段三:被唤醒后重新竞争锁
        T_A->>T_A: 被 unpark 唤醒(或等待前驱唤醒)
        T_A->>Sync: acquireQueued() 自旋竞争锁
        Sync-->>T_A: 获取锁成功,位于 head
        T_A->>T_A: await() 返回,继续执行
    end

2.3.2 await() 流程:从同步队列转移到条件队列

sequenceDiagram
    participant T as 当前线程(持有锁)
    participant Sync as 同步队列
    participant Cond as 条件队列
    participant OS as LockSupport

    Note over T: 线程已位于同步队列头部<br>(持有锁状态)
    
    T->>Cond: 1. addConditionWaiter()
    Note over Cond: 创建新节点,状态设为 CONDITION<br>加入条件队列尾部
    Cond-->>T: 返回节点 node

    T->>Sync: 2. fullyRelease(node)
    Note over Sync: 调用 release(savedState)<br>完全释放锁(包括重入次数)<br>唤醒同步队列中的后继节点
    Sync-->>T: 返回 savedState

    loop 3. 自旋等待转移
        T->>Sync: isOnSyncQueue(node)?
        alt 不在同步队列
            Sync-->>T: false
            T->>OS: LockSupport.park(this)
            Note over T,OS: 线程阻塞,等待 signal 唤醒
            OS-->>T: 被唤醒(signal 或中断)
            T->>T: checkInterruptWhileWaiting()
        else 已在同步队列
            Sync-->>T: true
            Note over T: 退出循环
        end
    end

    T->>Sync: 4. acquireQueued(node, savedState)
    Note over Sync: 在同步队列中自旋/阻塞<br>重新竞争锁,恢复重入计数
    Sync-->>T: 获取锁成功

    T->>Cond: 5. unlinkCancelledWaiters()
    Note over Cond: 清理条件队列中已取消的节点

    Note over T: await() 返回,线程继续执行

2.3.3 signal() 流程:从条件队列转移到同步队列

sequenceDiagram
    participant T2 as 调用signal的线程(持有锁)
    participant Cond as 条件队列
    participant Sync as 同步队列
    participant OS as LockSupport
    participant T1 as 被唤醒的线程

    Note over T2: 前提:T2 必须持有锁<br>(位于同步队列头部)

    T2->>Cond: 1. isHeldExclusively() 检查
    Cond-->>T2: true

    T2->>Cond: 2. doSignal(firstWaiter)
    Note over Cond: 摘除条件队列头部节点 first
    
    T2->>Cond: 3. transferForSignal(first)
    
    rect rgb(240, 248, 255)
        Note over Cond,Sync: 节点状态转换与迁移
        Cond->>Cond: CAS waitStatus: CONDITION → 0
        Cond->>Sync: enq(node) 加入同步队列尾部
        Sync-->>Cond: 返回前驱节点 p
    end

    alt 前驱已取消 或 CAS 设置 SIGNAL 失败
        Cond->>OS: LockSupport.unpark(T1)
        OS-->>T1: 唤醒信号
        Note over T1: 提前唤醒,自行处理同步队列状态
    else 正常情况
        Note over Cond: 不主动唤醒<br>等待前驱释放锁时被动唤醒
    end

    T2->>Cond: firstWaiter 后移
    Note over T2: signal() 返回

2.3.4 状态说明

阶段节点位置waitStatus线程状态
初始(持有锁)同步队列头部SIGNAL(可能)RUNNABLE
await() 后条件队列CONDITIONWAITING (park)
signal() 后同步队列尾部0 → SIGNALWAITING 或 RUNNABLE
重新获取锁后同步队列头部SIGNAL(可能)RUNNABLE

核心要点:节点永远不会同时存在于两个队列中await() 使其从同步队列消失、出现在条件队列;signal() 使其从条件队列消失、出现在同步队列尾部。


三、signal() 方法详解

signal() 将条件队列中等待时间最长的节点(firstWaiter)转移到同步队列。

3.1 完整源码分析

public final void signal() {
    // 前提:当前线程必须持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        // 将 firstWaiter 后移
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;  // 断开与条件队列的连接
    } while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
    // ① CAS 将节点状态从 CONDITION 改为 0(初始状态)
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;  // 节点已被取消,跳过

    // ② 将节点加入同步队列尾部,返回前驱节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // ③ 如果前驱已取消 或 CAS 设置 SIGNAL 失败,则直接唤醒线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

3.2 关键步骤剖析

  1. 状态转换CONDITION0。如果 CAS 失败,说明节点已被取消(如中断),直接返回 false
  2. 加入同步队列:调用 enq(node) 原子地挂到同步队列尾部。
  3. 唤醒策略:正常情况下,节点加入同步队列后不会立即唤醒,而是等待前驱释放锁时被动唤醒。但有两种例外会提前唤醒
    • 前驱节点已取消(ws > 0)。
    • CAS 设置前驱状态为 SIGNAL 失败。
    此时直接 unpark 线程,让它自己在同步队列中处理(重新设置 SIGNAL 或跳过取消节点)。

3.3 signalAll() 的区别

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

遍历整个条件队列,将所有节点逐个转移到同步队列。


四、中断与超时处理

4.1 中断模式分类

中断时机transferAfterCancelledWait(node) 返回值最终处理标记
signal 之前中断true抛出 InterruptedExceptionTHROW_IE
signal 之后中断false仅设置中断状态,不抛异常REINTERRUPT
final boolean transferAfterCancelledWait(Node node) {
    // 尝试将状态从 CONDITION 改为 0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        // 成功:说明 signal 还没执行,自己入队
        enq(node);
        return true;   // 中断发生在 signal 之前
    }
    // 失败:说明 signal 正在执行,等待它完成转移
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;      // 中断发生在 signal 之后
}

设计意图:保证先被 signal 的线程不会因为后续中断而丢失锁,从而维持条件变量的正确语义。

4.2 超时版本

ConditionObject 提供了多种超时等待方法,其实现与 await() 相似,主要差异在阻塞阶段:

方法阻塞方式返回值
awaitNanos(long)LockSupport.parkNanos剩余超时纳秒数,≤0 表示超时
awaitUntil(Date)计算截止时间,parkNanosbooleantrue 表示被唤醒,false 表示超时
await(long, TimeUnit)转换为纳秒,调用 awaitNanosboolean

awaitNanos 为例:

public final long awaitNanos(long nanosTimeout) throws InterruptedException {
    // ... 类似 await() 的前置处理
    while (!isOnSyncQueue(node)) {
        if (nanosTimeout <= 0L) {
            transferAfterCancelledWait(node);  // 超时,取消等待
            break;
        }
        nanosTimeout = LockSupport.parkNanos(this, nanosTimeout);
        // 检查中断...
    }
    // ... 后续竞争锁和清理
}

五、与 Object.wait/notify 的对比

维度synchronized + wait/notifyLock + Condition
条件队列数量每个对象唯一一把锁可创建多个 Condition
唤醒精确性notify() 随机唤醒一个;notifyAll() 全唤醒signal() 精确唤醒一个特定条件队列的线程
中断处理抛出 InterruptedException,无法区分时机区分 signal 前/后中断,保证锁语义
超时控制wait(long) 毫秒级支持纳秒、绝对时间点等多种方式
锁持有检查隐式(必须在 synchronized 块内)显式调用 isHeldExclusively() 检查

六、完整生命周期示意图

stateDiagram-v2
    [*] --> 持有锁: 线程获取锁成功
    持有锁 --> 条件队列: await()
    条件队列 --> 条件队列: park() 阻塞
    条件队列 --> 同步队列: signal() / signalAll()
    条件队列 --> 同步队列: 中断(在signal前) / 超时
    同步队列 --> 同步队列: park() / 自旋 等待锁
    同步队列 --> 持有锁: 获取锁成功
    持有锁 --> [*]: unlock() 释放锁

七、总结

核心要点说明
数据结构单向链表,复用 NodenextWaiter,状态恒为 CONDITION
await() 流程入条件队列 → 完全释放锁 → 阻塞 → 被转移后重新竞争锁
signal() 流程摘除队首节点 → 状态从 CONDITION0 → 加入同步队列尾部
中断处理区分 signal 前/后,保证被唤醒线程不会因中断而丢失锁
多条件支持一把锁可创建多个 ConditionObject,实现精细化线程调度
与同步队列协作节点在条件队列同步队列之间迁移,不同时存在于两边

ConditionObject 是 AQS 框架中组合优于继承的典范。它复用了 AQS 的 Node 结构、enq 入队、acquireQueued 竞争、LockSupport 阻塞等底层设施,仅用约 400 行代码就实现了一个功能完整的条件变量,充分展现了 AQS 设计的扩展性和精妙性。