notify只唤醒一个随机线程,不可控且易导致条件不匹配;notifyAll更安全,所有线程重新检查守卫条件,JDK并发工具均采用它;正确写法:wait必须在synchronized内、用while循环、配合notifyAll。

notify 只唤醒一个线程,但你根本不知道唤醒的是谁
调用 notify() 时,JVM 会从该对象的 wait set(等待队列)里随机挑一个线程唤醒——没有优先级、不看等待时长、不按顺序,纯看 JVM 实现和当前调度状态。这意味着:
- 如果多个线程在等不同条件(比如消费者 A 等数据 > 10,消费者 B 等数据 % 2 == 0),
notify()可能唤醒 B,而此时数据是 11,B 检查失败又调回wait(),A 却永远没被叫醒 - 线程池中复用线程时,旧的
wait()状态可能残留,导致“本不该等的人却在等”,notify()唤醒它后逻辑错乱 - 哪怕你写的是单生产者-单消费者,一旦加了异常处理、重试逻辑或监控线程,就很难保证“队列里真只有一个线程在等”
notifyAll 是默认更安全的选择,不是“重量级替代方案”
notifyAll() 把所有等待线程都推入锁竞争队列,让它们各自重新检查守卫条件(guard condition)。这不是浪费,而是把“谁该干活”的判断权交还给业务逻辑本身:
- 所有线程醒来后都必须用
while (!condition) { wait(); },而不是if——这是防虚假唤醒(spurious wakeup)的硬性要求 - 现代 JVM 对
notifyAll()有优化:没抢到锁的线程不会真正执行,只是从 wait set 移到 entry set,开销远小于一次死锁或活锁带来的线上故障 - JDK 自带的并发工具类(如
ArrayBlockingQueue、LinkedBlockingQueue)全部使用notifyAll(),连官方都不信notify()的可控性
什么时候真能用 notify?几乎不存在
文档里常写的“当只有一个线程在等时可用 notify()”,在真实工程中极难成立。你得同时满足:
- 整个生命周期内,该锁对象上
wait()的调用路径完全封闭(无继承、无代理、无 AOP 增强) - 所有异常分支都确保清理等待状态(比如 catch 里手动
interrupt()或标记退出) - 单元测试穷举了所有线程调度组合,并验证了 wait set 长度恒为 0 或 1
现实中,连日志打印、监控埋点、Spring 的事务拦截器都可能悄悄引入额外等待线程——所以别赌。
立即学习“Java免费学习笔记(深入)”;
正确写法就两条铁律
无论用哪个,只要涉及 wait()/notify(),就必须遵守:
-
wait()必须放在synchronized块内,且与notify()/notifyAll()使用**同一个锁对象** -
wait()外层必须是while循环,绝不能是if;通知端修改完条件后,统一用lock.notifyAll()
比如生产者发完数据后:
synchronized (dataLock) {
dataList.add(item);
dataReady = true;
dataLock.notifyAll(); // 不是 notify()
}
消费者端:
synchronized (dataLock) {
while (!dataReady) {
dataLock.wait();
}
// 处理数据
}
真正容易被忽略的,不是语法,而是“条件变量是否被所有相关线程共用且可见”——volatile 或同步块缺一不可。否则 notifyAll() 唤醒了人,人却看不到条件已变。










