AQS-ConditionObject详解

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


一、ConditionObject 的数据结构

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

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

队列结构示意

text 复制代码
firstWaiter                         lastWaiter
     ↓                                  ↓
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│    Node     │ →  │    Node     │ →  │    Node     │
│ waitStatus  │    │ waitStatus  │    │ waitStatus  │
│ =CONDITION  │    │ =CONDITION  │    │ =CONDITION  │
│ nextWaiter  │    │ nextWaiter  │    │ nextWaiter  │
└─────────────┘    └─────────────┘    └─────────────┘

与同步队列的关键区别

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

二、await() 方法详解

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

2.1 完整源码分析

java 复制代码
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():加入条件队列

java 复制代码
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):完全释放锁

java 复制代码
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):判断节点是否已转移

java 复制代码
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):处理等待期间的中断

java 复制代码
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: 线程已位于同步队列头部
(持有锁状态) T->>Cond: 1. addConditionWaiter() Note over Cond: 创建新节点,状态设为 CONDITION
加入条件队列尾部 Cond-->>T: 返回节点 node T->>Sync: 2. fullyRelease(node) Note over Sync: 调用 release(savedState)
完全释放锁(包括重入次数)
唤醒同步队列中的后继节点 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: 在同步队列中自旋/阻塞
重新竞争锁,恢复重入计数 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 必须持有锁
(位于同步队列头部) 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: 不主动唤醒
等待前驱释放锁时被动唤醒 end T2->>Cond: firstWaiter 后移 Note over T2: signal() 返回

2.3.4 状态说明

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

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


三、signal() 方法详解

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

3.1 完整源码分析

java 复制代码
public final void signal() {
    // 前提:当前线程必须持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
java 复制代码
private void doSignal(Node first) {
    do {
        // 将 firstWaiter 后移
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;  // 断开与条件队列的连接
    } while (!transferForSignal(first) && (first = firstWaiter) != null);
}
java 复制代码
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() 的区别

java 复制代码
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 抛出 InterruptedException THROW_IE
signal 之后中断 false 仅设置中断状态,不抛异常 REINTERRUPT
java 复制代码
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) 计算截止时间,parkNanos booleantrue 表示被唤醒,false 表示超时
await(long, TimeUnit) 转换为纳秒,调用 awaitNanos boolean

awaitNanos 为例:

java 复制代码
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/notify Lock + 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 设计的扩展性和精妙性。

相关推荐
张np2 小时前
java框架和http调用接口的区别
java·开发语言·http
web3.08889992 小时前
某宝店铺商品全量接口-item_search_shop_pro
java·服务器·数据库
朱一头zcy2 小时前
Java基础复习07:异常处理(编译时异常处理、运行时异常处理、try-catch-finally、自定义异常)
java·笔记·异常处理
手握风云-2 小时前
JavaEE 初阶第三十期:JVM,一次Full GC的架构级思考(上)
java·java-ee
ch.ju2 小时前
Java程序设计第二章——java数据类型:字符 转义字符
java
辉博士2 小时前
Spring Boot+EasyExcel实现Excel文件
java·spring boot·excel
小松加哲2 小时前
MyBatis完整流程详解
java·开发语言·mybatis
码码哈哈0.02 小时前
Spring AI 1.0.0 + ChromaDB 最新版踩坑:Collection does not exist 404 报错全记录
java·人工智能·spring
开开心心就好2 小时前
操作简单的ISO文件编辑转换工具
java·前端·科技·edge·pdf·安全威胁分析·ddos