AQS Condition 条件队列源码解析:ReentrantLock 精准等待 / 唤醒核心
哈喽,各位程序员伙伴~👋
上一篇我们吃透了 AQS 共享模式与 Semaphore,彻底掌握了 JUC 中「多线程共享资源、流量控制」的底层逻辑。而在并发编程中,除了互斥锁、共享限流,线程间的等待 / 唤醒是另一大核心场景 —— 生产者消费者、顺序执行、条件触发等业务,都离不开可靠的等待唤醒机制。
Java 原生的 Object.wait()/notify() 基于 JVM 监视器锁,存在单条件队列、无法精准唤醒、易虚假唤醒等缺陷,而 JUC 基于 AQS 实现的 Condition 条件队列,完美解决了这些问题,是 ReentrantLock 配套的「高级等待唤醒工具」,也是 AQS 四大核心组件(同步队列、独占模式、共享模式、条件队列)的最后一块拼图。
这篇从核心设计→源码拆解→实战对比→避坑落地,把 Condition 彻底讲透。
一、开篇先纠偏:Condition 解决了什么核心问题?
先对比原生等待唤醒的痛点,一眼看懂 Condition 的价值:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
核心结论: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. 释放锁(必须释放,否则死锁),返回释放前的 statelong 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;elset.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 改为 0if (!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 核心适用场景
- 生产者消费者模型
创建两个 Condition,生产者满队列等待、消费者空队列等待,精准唤醒; - 多条件顺序执行
线程 1 执行完唤醒线程 2,线程 2 执行完唤醒线程 3,无多余唤醒; - 自定义同步器
实现带复杂条件的锁(如带过期、带权限的锁); - 池化资源等待
连接池、线程池空池时线程等待,有资源时精准唤醒。
(二)、实战避坑技巧(高频必坑)
- 坑点 1:未加锁调用 await ()/signal ()
必抛 IllegalMonitorStateException,必须在 lock () 后、unlock () 前调用; - 坑点 2:await () 不处理中断
建议使用 awaitUninterruptibly()(不响应中断)或捕获中断异常,避免线程异常退出; - 坑点 3:混淆 signal () 和 signalAll ()
单线程等待用 signal(),多线程等待用signalAll(),避免线程永久等待; - 坑点 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();}}
七、核心总结
-
Condition 是 AQS 提供的多条件精准等待唤醒工具,基于独立单向条件队列实现; -
核心流程:抢锁→条件不满足→释放锁→条件队列等待→被转移至同步队列→重新抢锁; -
必须配合 ReentrantLock 使用, await()/signal()均需在加锁后调用; -
相比原生 wait/notify,支持多队列、精准唤醒、灵活中断,是高并发场景首选。
下一篇预告
这篇我们补齐了 AQS 最后一块核心拼图 ——Condition 条件队列,彻底掌握了 JUC 线程同步、等待唤醒的全底层逻辑。下一篇,我们将进入 JUC 原子类 领域,拆解 《Atomic 原子类源码解析:CAS 无锁编程,高效解决原子性问题》,从 Unsafe CAS 到 LongAdder 分段累加,吃透无锁并发的核心设计。
点赞 + 收藏,关注后续更新,让我们一起把 Java 并发底层彻底啃透~
夜雨聆风
