乐于分享
好东西不私藏

AQS Condition 条件队列源码解析:ReentrantLock 精准等待 / 唤醒核心

AQS Condition 条件队列源码解析:ReentrantLock 精准等待 / 唤醒核心

哈喽,各位程序员伙伴~👋

上一篇我们吃透了 AQS 共享模式与 Semaphore,彻底掌握了 JUC 中「多线程共享资源、流量控制」的底层逻辑。而在并发编程中,除了互斥锁、共享限流,线程间的等待 / 唤醒是另一大核心场景 —— 生产者消费者、顺序执行、条件触发等业务,都离不开可靠的等待唤醒机制。

Java 原生的 Object.wait()/notify() 基于 JVM 监视器锁,存在单条件队列、无法精准唤醒、易虚假唤醒等缺陷,而 JUC 基于 AQS 实现的 Condition 条件队列,完美解决了这些问题,是 ReentrantLock 配套的「高级等待唤醒工具」,也是 AQS 四大核心组件(同步队列、独占模式、共享模式、条件队列)的最后一块拼图。

这篇从核心设计→源码拆解→实战对比→避坑落地,把 Condition 彻底讲透。

一、开篇先纠偏:Condition 解决了什么核心问题?

先对比原生等待唤醒的痛点,一眼看懂 Condition 的价值:

特性
Object.wait()/notify()
Condition.await()/signal()
队列数量
1 个条件队列
一个锁可绑定多个条件队列
唤醒精度
notify () 随机唤醒一个在该对象上等待的线程,notifyAll () 全量唤醒
精准唤醒指定条件队列的线程
绑定锁
绑定 synchronized 监视器锁
绑定 ReentrantLock(AQS)
中断支持
支持中断
支持响应中断 / 不响应中断 / 超时三种模式
使用门槛
低,易出错
规范,精准,可靠性更高

核心结论:Condition 是 AQS 提供的多条件精准等待唤醒工具,本质是「独立于同步队列的条件等待队列」,线程在不满足业务条件时进入条件队列等待,条件满足后被精准唤醒,重新进入 AQS 同步队列抢锁。

二、核心铺垫:AQS 条件队列基础结构

Condition 的核心实现是 AQS 内部类 ConditionObject,完全依托 AQS 的 state、同步队列、LockSupport 阻塞唤醒实现,无额外冗余设计:

public abstract class AbstractQueuedSynchronizer {    // Condition 核心实现类    public class ConditionObject implements Condition, java.io.Serializable {        // 条件队列:单向链表(区别于同步队列的双向链表)        private transient Node firstWaiter;        private transient Node lastWaiter;        public ConditionObject() {}    }    // AQS 同步队列节点(复用,条件队列共用 Node 结构)    static final class Node {        // 节点状态:CONDITION 表示节点在条件队列中        static final int CONDITION = -2;        // 条件队列下一个等待节点        Node nextWaiter;    }}

核心设计一句话总结

  • 一个 Lock 对应多个 Condition
    每个 Condition 拥有独立的单向条件队列
  • 节点状态区分
    Node.CONDITION 标记节点在条件队列,非此状态则在同步队列;
  • 流程闭环
    线程抢锁成功 → 条件不满足 → 释放锁 → 进入条件队列等待 → 被 signal 唤醒 → 回到同步队列抢锁 → 执行业务。

三、Condition 核心源码解析

聚焦 3 个核心方法:await()(等待)、signal()(唤醒单个)、signalAll()(唤醒全部),全程贴合源码逻辑,无简化偏差。

1. 核心等待:await () 源码

线程调用 await(),会释放锁→进入条件队列→阻塞等待→被唤醒后重新抢锁,是 Condition 的核心逻辑:

public final void await() throws InterruptedException {    // 1. 响应中断,线程中断直接抛异常    if (Thread.interrupted())        throw new InterruptedException();    // 2. 将当前线程封装为 CONDITION 状态节点,加入条件队列尾部    Node node = addConditionWaiter();    // 3. 释放锁(必须释放,否则死锁),返回释放前的 state    long savedState = fullyRelease(node);    boolean interrupted = false;    // 4. 循环判断:节点是否回到同步队列,未回到则持续阻塞    while (!isOnSyncQueue(node)) {        // 4.1 阻塞线程(LockSupport.park())        LockSupport.park(this);        // 4.2 被唤醒后检查中断        if ((interrupted = checkInterruptWhileWaiting(node)) != 0)            break;    }    // 5. 被唤醒后,重新进入 AQS 同步队列抢锁(独占模式)    if (acquireQueued(node, savedState) && interrupted != THROW_IE)        interrupted = REINTERRUPT;    // 6. 清理取消的节点    if (node.nextWaiter != null)        unlinkCancelledWaiters();    // 7. 处理中断状态    if (interrupted != 0)        reportInterruptAfterWait(interrupted);}// 辅助方法:添加条件队列节点private Node addConditionWaiter() {    Node t = lastWaiter;    // 清理已取消的节点    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;}

关键严谨结论

  • await()必须在加锁后调用,否则fullyRelease() 会抛
IllegalMonitorStateException
  • 调用 await() 会完全释放锁,保证其他线程能抢锁修改条件;
  • 线程阻塞期间仅存在于条件队列,不占用锁资源。

2. 核心唤醒:signal () 源码

唤醒条件队列中第一个有效节点,将其从条件队列移至 AQS 同步队列,等待抢锁:

public final void signal() {    // 1. 校验:必须持有锁才能调用 signal()    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    Node first = firstWaiter;    // 2. 唤醒条件队列第一个节点    if (first != null)        doSignal(first);}// 辅助方法:执行唤醒(转移节点到同步队列)private void doSignal(Node first) {    do {        // 移除条件队列头节点        if ((firstWaiter = first.nextWaiter) == null)            lastWaiter = null;        first.nextWaiter = null;        // 3. 将节点从条件队列转移到同步队列,失败则继续唤醒下一个    } while (!transferForSignal(first) && (first = firstWaiter) != null);}// 核心:转移节点到同步队列final boolean transferForSignal(Node node) {    // 1. CAS 将节点状态从 CONDITION 改为 0    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))        return false;    // 2. 将节点加入 AQS 同步队列尾部    Node p = enq(node);    int ws = p.waitStatus;    // 3. 唤醒线程,使其进入同步队列抢锁    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))        LockSupport.unpark(node.thread);    return true;}

3. 全量唤醒:signalAll () 源码

唤醒条件队列所有有效节点,全部转移至同步队列:

public final void signalAll() {    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    Node first = firstWaiter;    if (first != null)        doSignalAll(first);}// 遍历条件队列,转移所有节点到同步队列private void doSignalAll(Node first) {    lastWaiter = firstWaiter = null;    do {        Node next = first.nextWaiter;        first.nextWaiter = null;        transferForSignal(first);        first = next;    } while (first != null);}

唤醒核心规则

  • signal()/signalAll()必须在加锁后调用
  • 唤醒仅做节点转移(条件队列→同步队列),不直接执行线程;
  • 被唤醒线程需重新抢锁,保证线程安全。

四、ReentrantLock 中的 Condition 实现

ReentrantLock 直接复用 AQS 的 ConditionObject,使用极简:

public class ReentrantLock implements Lock {    private final Sync sync;    // 创建 Condition 实例,绑定当前锁    public Condition newCondition() {        return sync.newCondition();    }    abstract static class Sync extends AbstractQueuedSynchronizer {        final ConditionObject newCondition() {            return new ConditionObject();        }    }}

五、个性化工程感悟

Condition 看似只是「等待 / 唤醒」的封装,本质是 AQS 对「线程同步粒度」的极致细化:原生 wait/notify 把所有等待线程塞到一个队列里,唤醒时「一刀切」,既低效又容易出错;而 Condition 让一个锁可以拆分出多个条件队列(如生产者队列、消费者队列),实现条件级别的精准等待与唤醒,彻底避免无效唤醒与资源浪费。

这种「按条件分组、精准调度」的设计思想,不仅适用于线程同步,更能迁移到业务层的任务调度、消息队列、事件驱动等场景,是高并发编程「精细化控制」的经典体现。

六、场景化延伸 + 实战指引(落地可执行)

(一)、场景化延伸:Condition 核心适用场景

  1. 生产者消费者模型
    创建两个 Condition,生产者满队列等待、消费者空队列等待,精准唤醒;
  2. 多条件顺序执行
    线程 1 执行完唤醒线程 2,线程 2 执行完唤醒线程 3,无多余唤醒;
  3. 自定义同步器
    实现带复杂条件的锁(如带过期、带权限的锁);
  4. 池化资源等待
    连接池、线程池空池时线程等待,有资源时精准唤醒。

(二)、实战避坑技巧(高频必坑)

  1. 坑点 1:未加锁调用 await ()/signal ()
    必抛 IllegalMonitorStateException必须在 lock () 后、unlock () 前调用
  2. 坑点 2:await () 不处理中断
    建议使用 awaitUninterruptibly()(不响应中断)或捕获中断异常,避免线程异常退出;
  3. 坑点 3:混淆 signal () 和 signalAll ()
    单线程等待用 signal(),多线程等待用 signalAll(),避免线程永久等待;
  4. 坑点 4:忘记唤醒导致线程阻塞
    条件满足后必须调用 signal()/signalAll(),否则条件队列线程永远阻塞。

(三)、实战标准模板(生产者消费者,直接复制)

ReentrantLock lock = new ReentrantLock();// 空队列条件:消费者等待Condition emptyCond = lock.newCondition();// 满队列条件:生产者等待Condition fullCond = lock.newCondition();Queue<Object> queue = new LinkedList<>();int capacity = 10;// 生产者publicvoidproduce(Object obj) {    lock.lock();    try {        while (queue.size() == capacity) {            fullCond.await(); // 满队列,进入条件等待        }        queue.add(obj);        emptyCond.signal(); // 唤醒消费者    } finally {        lock.unlock();    }}// 消费者public Object consume() {    lock.lock();    try {        while (queue.isEmpty()) {            emptyCond.await(); // 空队列,进入条件等待        }        Object obj = queue.poll();        fullCond.signal(); // 唤醒生产者        return obj;    } finally {        lock.unlock();    }}

七、核心总结

  1. Condition 是 AQS 提供的多条件精准等待唤醒工具,基于独立单向条件队列实现;
  2. 核心流程:抢锁→条件不满足→释放锁→条件队列等待→被转移至同步队列→重新抢锁;
  3. 必须配合 ReentrantLock 使用,await()/signal() 均需在加锁后调用;
  4. 相比原生 wait/notify,支持多队列、精准唤醒、灵活中断,是高并发场景首选。

下一篇预告

这篇我们补齐了 AQS 最后一块核心拼图 ——Condition 条件队列,彻底掌握了 JUC 线程同步、等待唤醒的全底层逻辑。下一篇,我们将进入 JUC 原子类 领域,拆解 《Atomic 原子类源码解析:CAS 无锁编程,高效解决原子性问题》,从 Unsafe CAS 到 LongAdder 分段累加,吃透无锁并发的核心设计。

点赞 + 收藏,关注后续更新,让我们一起把 Java 并发底层彻底啃透~

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » AQS Condition 条件队列源码解析:ReentrantLock 精准等待 / 唤醒核心

评论 抢沙发

5 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮