怎么利用 Condition.await 与 signal 实现线程间的精准定点唤醒
Condition无法直接唤醒指定线程,需为不同等待意图创建独立Condition实例,每个维护独立等待队列,signal仅唤醒队列头部线程。配合while循环检查条件变量防止虚假唤醒,signal须在持有锁时调用并随后释放锁。
很多人在使用 Condition.await() 和 signal() 来实现线程间的精准唤醒时,都会遇到一个困惑:为什么我不能直接唤醒某个特定的线程?其实,这个问题的答案并不复杂,关键在于理解 Condition 的工作机制——await() 本身并不保存调用线程的身份信息,线程挂起后进入 FIFO 等待队列,而 signal() 也只是唤醒队列头部的第一个线程,而非指定线程。所谓的“精准定点唤醒”,本质上依赖的是业务逻辑对等待和唤醒时机、对象的控制,而不是 Condition 直接提供的能力。

Condition.await 为什么不能直接唤醒指定线程
一个常见的错误场景是:多个线程共用同一个 Condition,期望通过 signal() 唤醒某个特定线程,结果却唤醒了别的线程,导致逻辑错乱甚至死锁。要避免这种问题,有几个关键点需要特别注意:
- 必须为不同的唤醒目标准备独立的
Condition实例(即通过多个ReentrantLock.newCondition()创建) - 每个线程必须明确知道自己该
await哪个Condition,并且只有对应的逻辑才能signal()它 - 避免在单个
Condition上混合多种等待语义——比如将“等数据就绪”和“等资源释放”混在一起
如何为不同线程分配专属 Condition 实例
分配的关键并非“给线程编号”,而是**按等待意图建模**。举个例子,线程 A 等待“缓冲区非空”,线程 B 等待“缓冲区未满”,它们天然就应该使用两个不同的 Condition:
private final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); // 供消费者 await private final Condition notFull = lock.newCondition(); // 供生产者 await
这种设计广泛应用于生产者-消费者模型、多阶段任务协调、状态机驱动的线程协作等场景。需要记住的是:
- 一个
ReentrantLock可以关联任意多个Condition,它们互不干扰 - 每个
Condition维护自己的等待队列,signal()只会影响其队列中的线程 - 不要复用
Condition实例来应对不同语义的场景,否则“定点”就无从谈起
await 必须配合 while 循环检查条件变量
即使为每个线程分配了专属的 Condition,await() 返回后也不能直接认为条件已满足——原因在于虚假唤醒(spurious wakeup)的存在,也可能唤醒后条件又被其他线程改回不满足状态。
一个典型的错误写法是:if (buffer.isEmpty()) notEmpty.await();——一旦唤醒就往下走,极大概率出错。正确的模式是:
while (!conditionHolds()) notEmpty.await();conditionHolds()必须是受同一把锁保护的共享状态判断,比如buffer.size() > 0- 每次
signal()后,务必在持有锁的前提下修改对应的条件变量,并显式调用signal()
signal 调用时机与锁持有关系
signal() 必须在持有对应 ReentrantLock 的前提下调用,否则会抛出 IllegalMonitorStateException。但更重要的是:signal 之后是否立即释放锁,会直接影响被唤醒线程能否立刻抢到锁继续执行。
性能上的差异显而易见:
- 推荐做法:在修改完条件变量后,**仍在锁内**调用
signal(),然后再unlock()。这样被唤醒线程有机会在唤醒后直接获取锁,减少上下文切换 - 危险做法:先
unlock()再signal()——此时唤醒的线程会立刻尝试抢锁失败,陷入二次阻塞,白白增加延迟 signal()不保证唤醒线程马上运行,只是将其从等待队列移到同步队列;最终调度仍由 JVM 和 OS 决定
总结一下,要实现真正的“精准定点唤醒”,核心在于两点:一是根据等待意图拆分 Condition 实例,二是用 while 循环配合锁保护的条件检查来确保可靠性。没有独立的 Condition,就没有定点;没有 while 加锁保护,就没有可靠性——这两者缺一不可。


































