前言ConditionObject维护条件队列AQS维护同步队列。互动体现在await()释放锁并进入条件队列等待signal()将节点从条件队列转移到同步队列。我们分大白话版和学术代码版来详细解释ConditionObject与 AQS 队列之间的互动。️ 大白话版银行VIP室与普通排队区的互动把 AQS 的同步队列想象成银行大厅的普通排队区所有人按顺序排队办业务而ConditionObject就像一个VIP 休息室条件等待区。情景你正在柜台办业务但柜员说“你要取的钱暂时不够需要等一会儿。你先去旁边的VIP休息室等着等钱到了我再叫你。”await()做的事去休息室等待释放锁你先把手头的业务放下释放持有的锁因为休息室的人不能占着柜台。进入休息室你走进 VIP 休息室进入条件队列安静地坐着。阻塞此时你不再参与大厅的排队竞争完全挂起等待通知。signal()做的事叫号从休息室出来柜员说“钱到了”把你从 VIP 休息室叫出来从条件队列中移除。回到排队区你重新进入大厅的普通排队区转移到 AQS 同步队列就像新来的客户一样排队等待。等待竞争等排到你的时候你再次获得锁然后继续之前没办完的业务。关键点条件队列和同步队列是两个独立的队列。await让你从同步队列“转移”到条件队列并阻塞signal让你从条件队列“转移”回同步队列重新竞争锁。 学术代码版我们从为什么需要和调用流程两个方面结合ReentrantLock来解释ConditionObject的作用。1. 为什么需要ConditionObjectReentrantLock提供了基本的互斥锁功能同一时刻只有一个线程能执行临界区代码。但在很多场景中光有互斥是不够的还需要线程之间的协调。比如经典的生产者-消费者模式当缓冲区空时消费者线程应该等待直到生产者放入数据。当缓冲区满时生产者线程应该等待直到消费者取出数据。用单纯的锁ReentrantLock只能保证存取操作互斥无法实现“条件不满足时主动阻塞并在条件满足时被唤醒”。如果让线程自己循环检查条件并调用Thread.sleep()效率极低且无法精确唤醒。ConditionObject正是为了解决这个问题它允许线程在某个条件不成立时释放锁并进入等待状态等其他线程改变了条件后精确地唤醒那些等待的线程。一句话Condition是锁上的“等待 - 通知”机制是Object.wait/notify的更强大、更灵活的替代品。2.ReentrantLock中使用ConditionObject的完整调用流程我们用一个有界队列的实例来演示整个流程。代码示例publicclassBoundedQueueT{privatefinalReentrantLocklocknewReentrantLock();// 两个条件对象队列非空、队列未满privatefinalConditionnotEmptylock.newCondition();// 内部是 ConditionObjectprivatefinalConditionnotFulllock.newCondition();privatefinalObject[]items;privateintputIndex,takeIndex,count;publicBoundedQueue(intcapacity){itemsnewObject[capacity];}// 生产者调用添加元素publicvoidput(Tt)throwsInterruptedException{lock.lock();// 1. 获取锁try{while(countitems.length){// 2. 条件不满足队列已满notFull.await();// 3. 等待“未满”条件}items[putIndex]t;// 4. 执行操作if(putIndexitems.length)putIndex0;count;notEmpty.signal();// 5. 唤醒等待“非空”条件的消费者}finally{lock.unlock();// 6. 释放锁}}// 消费者调用取出元素SuppressWarnings(unchecked)publicTtake()throwsInterruptedException{lock.lock();try{while(count0){// 条件不满足队列为空notEmpty.await();// 等待“非空”条件}Tt(T)items[takeIndex];if(takeIndexitems.length)takeIndex0;count--;notFull.signal();// 唤醒等待“未满”条件的生产者returnt;}finally{lock.unlock();}}}调用时序图以消费者线程等待为例消费者线程 生产者线程 | | | lock.lock() (获取锁) | | while (count 0) | | notEmpty.await() ------------ (释放锁进入条件队列阻塞) | | | | lock.lock() (获取锁) | | 放入元素count 变为 1 | | notEmpty.signal() -- | | lock.unlock() | | | | | -- 被唤醒从 await() 返回 -- | | 重新竞争锁 (在 AQS 同步队列中排队) | | 获得锁后继续执行 (取出元素) | | ... |详细步骤分解重点看await()和signal()的内部动作步骤消费者线程调用await()生产者线程调用signal()1调用lock.lock()成功获取锁初始时消费者持有锁并已调用await2条件count 0成立调用notEmpty.await()调用lock.lock()此时锁被消费者释放了吗 注意消费者在await中已经释放了锁所以生产者可以获取锁3await()内部动作- 将当前线程包装成Node加入ConditionObject的条件队列非 AQS 队列- 调用fullyRelease完全释放锁包括重入次数- 阻塞线程 (LockSupport.park)signal()内部动作- 从ConditionObject的条件队列中取出第一个Node- 将该Node从条件队列中移除- 调用enq(node)将Node转移到 AQS 的同步队列等待锁的队列- 修改Node的状态4线程阻塞不再参与锁竞争可选如果转移后前驱节点状态正常不会立即唤醒等待生产者释放锁5等待被唤醒生产者调用lock.unlock()释放锁6-释放锁时AQS 会从同步队列中唤醒下一个节点可能正是刚刚转移过来的消费者线程7消费者线程被唤醒从park()返回继续执行await()的后半部分进入acquireQueued在同步队列中竞争锁-8消费者线程竞争到锁后从await()返回重新检查条件while (count 0)此时条件不成立因为生产者已放入元素退出循环执行取元素操作-3. 为什么这个流程需要ConditionObject而不是只用 AQS 同步队列AQS 的同步队列CLH 队列只负责管理等待锁的线程。如果一个线程因为业务条件不满足而等待它必须释放锁否则会造成死锁其他线程永远无法改变条件。同时它需要被精确唤醒而不是所有等待锁的线程。ConditionObject提供了独立的条件队列每个Condition对象有自己的队列允许同一把锁上存在多个不同的等待条件如notFull和notEmpty。await()让线程从同步队列转移到条件队列并释放锁。signal()/signalAll()让线程从条件队列转移回同步队列然后正常竞争锁。这样就实现了锁的释放与等待条件的分离而且比Object.wait/notify更灵活支持多条件、公平性选择等。4. 总结角色作用ReentrantLock提供互斥锁和可重入性ConditionObject提供等待/通知机制让线程在条件不满足时释放锁并阻塞条件满足时被唤醒调用流程核心await(): 释放锁 → 入条件队列 → 阻塞signal(): 出条件队列 → 入同步队列 → 等待锁 → 唤醒后重新竞争锁最终效果线程既保证了临界区的互斥又能高效地等待特定条件避免无效循环和忙等。这正是ReentrantLockCondition组合的强大之处。 ConditionObject 是如何与 AQS 队列互动的ConditionObject是AQS内部实现等待/通知机制的关键相当于为每个Condition对象维护了一个独立的条件队列。它与AQS主队列的互动生动展示了线程等待与唤醒的完整过程await(): 等待条件发生步骤1加入条件队列当前线程必须已持有锁会通过addConditionWaiter()将自己封装成一个Node并加入到ConditionObject的条件队列中。步骤2释放锁资源通过fullyRelease(node)将当前线程持有的锁全部释放包括所有重入次数并唤醒AQS队列中的后继线程。步骤3阻塞等待唤醒线程进入自旋只要它还在条件队列中!isOnSyncQueue(node)就会通过LockSupport.park(this)进入阻塞状态等待被唤醒。signal(): 唤醒等待线程步骤1取出节点从条件队列的头部取出第一个等待节点firstWaiter。步骤2节点转移将此Node从条件队列中移除并通过enq(node)方法安全地将其重新加入到AQS的主等待队列尾部。步骤3状态调整将节点的waitStatus从CONDITION重置为0为后续在AQS队列中正常排队做准备。signalAll(): 唤醒所有等待线程它会遍历整个条件队列将所有节点都移动到AQS的主队列中。这些被转移的线程将在主队列中竞争锁并最终一个接一个地获得执行机会。从源码剖析互动细节在AbstractQueuedSynchronizer内部ConditionObject维护了自己的条件队列单向链表而 AQS 本身维护同步队列双向链表。它们的互动主要通过await()和signal()完成。源码以JDK8为例。1️⃣await()做了什么 —— 释放锁 进入条件队列 阻塞// java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObjectpublicfinalvoidawait()throwsInterruptedException{if(Thread.interrupted())thrownewInterruptedException();// 1. 将当前线程封装成 Node加入条件队列NodenodeaddConditionWaiter();// 2. 释放当前线程持有的锁全部重入次数并唤醒同步队列中的后继节点intsavedStatefullyRelease(node);intinterruptMode0;// 3. 判断当前 node 是否还在同步队列中while(!isOnSyncQueue(node)){// 4. 不在同步队列中说明还在条件队列中就阻塞线程LockSupport.park(this);// 检查中断...}// 5. 被唤醒后进入同步队列的竞争流程if(acquireQueued(node,savedState)interruptMode!THROW_IE)interruptModeREINTERRUPT;// 后续处理中断...}关键互动点addConditionWaiter()将当前线程的Node加入ConditionObject的条件队列firstWaiter/lastWaiter。fullyRelease(node)释放锁让 AQS 同步队列中的下一个线程可以继续竞争。isOnSyncQueue(node)检查节点是否已经从条件队列转移到了 AQS 同步队列。如果还在条件队列中就park阻塞。只有当signal将其转移到同步队列后才会退出循环。退出循环后调用acquireQueued让线程在同步队列中正常排队竞争锁。2️⃣signal()做了什么 —— 将节点从条件队列移到同步队列// java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObjectpublicfinalvoidsignal(){// 1. 检查当前线程是否持有锁只有锁持有者才能调用 signalif(!isHeldExclusively())thrownewIllegalMonitorStateException();NodefirstfirstWaiter;if(first!null)doSignal(first);}privatevoiddoSignal(Nodefirst){do{// 2. 将 first 从条件队列中移除first first.nextWaiterif((firstWaiterfirst.nextWaiter)null)lastWaiternull;first.nextWaiternull;// 3. 将节点转移到 AQS 的同步队列中}while(!transferForSignal(first)(firstfirstWaiter)!null);}// 该方法在 AQS 中finalbooleantransferForSignal(Nodenode){// 1. 将节点的 waitStatus 从 CONDITION 设为 0表示不再是条件等待状态if(!compareAndSetWaitStatus(node,Node.CONDITION,0))returnfalse;// 2. 将该节点入队到 AQS 的同步队列尾部并返回该节点的前驱节点Nodepenq(node);intwsp.waitStatus;// 3. 如果前驱节点被取消或无法设为 SIGNAL则直接唤醒节点if(ws0||!compareAndSetWaitStatus(p,ws,Node.SIGNAL))LockSupport.unpark(node.thread);returntrue;}关键互动点从条件队列头部取出节点。将节点的waitStatus从CONDITION改为 0表示它不再是条件等待状态。通过enq(node)将该节点安全地加入 AQS 同步队列的尾部。如果有必要直接唤醒该线程让它从await()的park处恢复然后通过acquireQueued正常排队竞争锁。3️⃣ 完整流程图条件队列 ↔ 同步队列获取锁的线程 锁被释放 │ │ ▼ ▼ ┌─────────────┐ await() ┌─────────────┐ signal() ┌─────────────┐ │ AQS 同步队列 │ ──释放锁──▶ │ 条件队列 │ ──转移节点──▶ │ AQS 同步队列 │ │ (排队区) │ ◀─竞争锁─── │ (休息室) │ │ (排队区) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ 持有锁的线程 等待条件的线程 重新排队竞争锁 调用 await() 被唤醒后转移 最终获得锁 总结队列管理者存储内容节点转移方向AQS 同步队列AQS 本身正在等待锁的线程signal时从条件队列 → 同步队列条件队列ConditionObject等待某个条件成立的线程await时从同步队列 → 条件队列互动核心await释放锁 进入条件队列 阻塞。signal从条件队列移除 加入同步队列不自动解锁等待锁释放后线程自然竞争。这就是Condition实现线程间精确通知的基础将等待和锁的竞争完全分离由 AQS 统一管理阻塞和唤醒的底层细节。