Condition 的 await()/signal() 无法唤醒指定线程,只能唤醒同一 Condition 上任意一个等待线程;需通过多个 Condition 实例、volatile 状态变量、while 循环守卫及严格唤醒时机来模拟精准唤醒。

Condition 的 await() / signal() 不是“唤醒指定线程”,而是“唤醒等待在该 Condition 上的任意一个线程”
这是最常被误解的前提:ReentrantLock 的 Condition 没有 signal(Thread) 这种 API,signal() 只能唤醒一个处于 await() 状态的线程,但无法控制是哪一个——它依赖内部 FIFO 队列顺序,且受线程调度影响。所谓“按序唤醒”,本质是靠设计「多个独立 Condition 实例 + 状态守卫 + 严格的 await/signal 时机」来模拟。
典型错误是只用一个 Condition,然后试图靠 if 判断 + signal() 实现 A→B→C 的唤醒链,结果出现 B 被跳过、C 先醒、或虚假唤醒后逻辑错乱。
实操建议:
- 每个需要被独立唤醒的线程(或线程组)绑定专属
Condition实例,例如condA、condB、condC - 所有
await()必须包裹在 while 循环中,检查明确的状态变量(如state == 1),而非 if -
signal()只在状态变更后、且确认下一个环节已进入等待态时调用(否则信号丢失) - 避免在持有锁期间长时间执行非同步逻辑,否则会阻塞其他线程获取锁并检查状态
用 state 变量 + 多 Condition 实现三阶段顺序执行
假设线程 A、B、C 需严格按 A→B→C 执行,每阶段完成一次任务后唤醒下一阶段。核心不是“叫醒谁”,而是“谁在等我,我完成就通知它”。
示例关键结构:
private final ReentrantLock lock = new ReentrantLock(); private final Condition condA = lock.newCondition(); private final Condition condB = lock.newCondition(); private final Condition condC = lock.newCondition(); private volatile int state = 0; // 0: waiting for A, 1: A done, 2: B done, 3: C done
线程 A 的逻辑:
lock.lock();
try {
while (state != 0) condA.await(); // 等待轮到自己
doWorkA();
state = 1;
condB.signal(); // 唤醒 B(B 此时应在 await 状态)
} finally {
lock.unlock();
}
线程 B 的逻辑类似,检查 state == 1,完成后设 state = 2 并 signal(condC)。
注意点:
-
state必须是volatile或用AtomicInteger,确保可见性;仅靠锁不能保证跨线程状态读取及时 - 若 B 尚未调用
await()就执行signal(),该信号直接丢失(Condition无缓冲) - 启动时需先让 B、C 进入
await(),再唤醒 A;否则 A 完成后 signal 无效
signalAll() 和 signal() 的选择取决于“是否允许多个线程竞争同一条件”
在按序唤醒场景中,几乎总是用 signal(),而非 signalAll()。因为 signalAll() 会唤醒所有等待在该 Condition 上的线程,它们将竞争锁、重新检查守卫条件,大概率造成“本不该醒的也醒了”,破坏顺序性。
只有两种情况考虑 signalAll():
- 一个
Condition对应多个同质线程(如 5 个消费者线程共用notEmpty),且只要有一个能处理即可 - 你明确接受“唤醒多个 → 大家抢 → 大部分再 await 回去”的开销,且守卫条件足够健壮
按序唤醒里滥用 signalAll() 是性能和逻辑双坑:既浪费唤醒/调度资源,又可能让下游线程因状态不匹配而反复进出 await。
容易被忽略的“唤醒时机”和“虚假唤醒”防御
很多人把 await() 当作“暂停直到被 signal”,却忘了 JVM 规范允许虚假唤醒(spurious wakeup)。即使没被 signal,await() 也可能返回。所以 while 循环不是可选项,是必须项。
另一个隐形陷阱是唤醒时机错位:比如 A 完成后立刻 signal(condB),但此时 B 还没 lock 成功、更没走到 await(),信号就丢了。解决方案只有两个:
- 启动协调:用一个初始化屏障(如
CountDownLatch)确保 B、C 已进入await()后,再放行 A - 状态兜底:B 的 while 条件写成
while (state != 1 && state != 3)(加超时或中断响应),避免永久阻塞;但这已脱离“精准按序”范畴
真实系统中,这类逻辑越复杂,越建议用更高层抽象,比如 Phaser 或 CompletableFuture 链式编排——ReentrantLock + Condition 提供的是原子控制原语,不是流程引擎。

















