1. Java线程协作中的Condition机制解析
在Java并发编程中,Condition接口为线程间的精确协作提供了比传统wait/notify更灵活的控制手段。我首次在生产环境使用Condition是在实现一个高并发的订单状态机时,需要精确控制不同状态转换的线程唤醒条件。与基础的对象监视器方法相比,Condition的核心优势在于它能创建多个等待队列,实现更细粒度的线程调度。
2. Condition接口的核心设计原理
2.1 与Lock的绑定机制
每个Condition实例必须绑定到一个显式Lock上,这种设计确保了等待/通知操作与锁状态的严格对应。在AQS(AbstractQueuedSynchronizer)的实现中,ConditionObject作为内部类维护着条件队列:
final ConditionObject newCondition() { return new ConditionObject(); }关键细节:ConditionObject保存了firstWaiter和lastWaiter指针,构成一个FIFO的等待队列。与同步队列不同,条件队列中的节点状态固定为CONDITION(-2)。
2.2 等待队列与同步队列的转换
当线程调用await()时,会经历三个关键步骤:
- 完全释放持有的锁(包括重入计数)
- 创建CONDITION节点加入条件队列
- 进入阻塞状态等待signal
对应的核心代码段:
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); // 步骤2 int savedState = fullyRelease(node); // 步骤1 while (!isOnSyncQueue(node)) { LockSupport.park(this); // 步骤3 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 被唤醒后重新竞争锁 }3. 信号传递的底层实现
3.1 signal()的精确唤醒
signal()操作会将条件队列的首节点转移到同步队列,这个过程包含几个关键操作:
- 校验调用线程是否持有锁(未持有锁会抛出IllegalMonitorStateException)
- 将firstWaiter从条件队列移除
- 通过enq()方法将节点加入同步队列尾部
- 如果前驱节点已取消或设置状态失败,会触发unpark()立即唤醒线程
public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); }3.2 signalAll()的批量转移
与signal()不同,signalAll()会遍历整个条件队列,将所有等待节点转移到同步队列。这里有个性能优化点:转移后的节点会保持原有顺序,避免线程饥饿问题。
4. 条件谓词与状态检查的最佳实践
4.1 经典的生产者-消费者实现
下面是一个使用Condition的线程安全队列实现:
public class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }重要细节:条件检查必须使用while循环而非if语句,这是应对"虚假唤醒"的标准做法。即使没有调用signal,JVM也允许线程从await()中返回。
5. 性能优化与陷阱规避
5.1 条件队列的选择策略
- 单条件队列:适用于状态变化简单的场景(如简单的互斥锁)
- 多条件队列:适合存在多个独立等待条件的场景(如读写锁中的读/写条件)
- 混合模式:在复杂的状态机实现中,可以为每个重要状态转换创建独立条件
5.2 常见问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| IllegalMonitorStateException | 未持有锁时调用signal/await | 确保在lock()和unlock()之间操作Condition |
| 线程未被唤醒 | signal在await之前调用 | 使用带超时的await或检查条件谓词逻辑 |
| CPU占用过高 | 未使用while检查条件 | 将所有if(condition)改为while(condition) |
| 顺序错乱 | 多个条件队列交叉使用 | 为每个独立条件创建单独的Condition实例 |
6. 与其它同步机制的对比
6.1 Condition vs Object Monitor
| 特性 | Condition | Object Monitor |
|---|---|---|
| 多等待队列 | 支持 | 不支持 |
| 超时控制 | 提供awaitNanos等方法 | 只有wait(long) |
| 中断响应 | 可区分中断类型 | 统一抛出InterruptedException |
| 公平性 | 依赖Lock实现 | 由JVM控制 |
6.2 Condition在并发库中的应用
- ThreadPoolExecutor:使用Condition实现工作线程的等待/唤醒
- ArrayBlockingQueue:用notFull和notEmpty两个条件管理队列状态
- CyclicBarrier:结合Condition和Generation实现批次控制
7. 高级应用模式
7.1 可重入的条件检查
在复杂状态机中,可以实现条件检查的嵌套调用:
public void transfer(Account from, Account to, int amount) { from.lock.lock(); try { while (from.balance < amount) { from.sufficientFunds.await(); } to.lock.lock(); try { from.balance -= amount; to.balance += amount; to.sufficientFunds.signalAll(); } finally { to.lock.unlock(); } } finally { from.lock.unlock(); } }死锁预防:必须按照固定的全局顺序获取锁,通常可以通过System.identityHashCode()比较对象哈希值来确定顺序。
7.2 条件队列的监控技巧
通过反射可以获取条件队列的实时状态(仅限调试):
Field firstWaiter = ConditionObject.class.getDeclaredField("firstWaiter"); firstWaiter.setAccessible(true); int queueLength = 0; for (Node node = (Node) firstWaiter.get(condition); node != null; node = node.nextWaiter) { queueLength++; } System.out.println("等待线程数: " + queueLength);在实际项目中,我会在Condition实现类中添加监控逻辑,通过JMX暴露等待线程数等关键指标。