notify()随机唤醒一个线程,无法控制唤醒对象,易导致条件不满足而卡死;notifyAll()唤醒全部线程并各自重检条件,更安全但开销略大;无论哪种都必须用while而非if检查条件。

notify() 只唤醒一个线程,但你无法控制唤醒谁
这是最常被误解的一点:notify() 并不是“唤醒等待最久的”或“唤醒符合条件的”,而是由 JVM 从当前对象的等待队列(Wait Set)中**随机挑一个**线程唤醒。如果恰好挑中了逻辑上不该此时执行的线程(比如它依赖的条件还没真正满足),就会导致程序卡死、数据不一致或无限等待。
- 调用
notify()后,其余仍在wait()的线程继续沉睡,不会自动重检条件 - 被唤醒的线程必须重新竞争对象锁,拿到锁后才从
wait()返回,接着执行后续代码 - 若唤醒的线程发现条件仍不成立(例如缓冲区还是空的),它必须再次
wait()—— 但此时没人再notify(),就可能永久挂起
notifyAll() 唤醒全部,让每个线程自己决定要不要继续
notifyAll() 把等待队列里所有线程都推入锁竞争池(Entry Set),它们会一起抢锁;抢到锁的线程执行,没抢到的则阻塞等待锁释放。关键在于:**每个被唤醒的线程都会重新检查自己的等待条件**——这正是它比 notify() 更安全的根本原因。
- 适用于多个线程等待同一对象、但关注不同子条件的场景(如:消费者等“有数据”,生产者等“有空位”)
- 避免“虚假唤醒”(spurious wakeup)导致的逻辑遗漏:即使没被显式通知,线程也可能被唤醒,所以必须用
while而非if检查条件 - 性能开销确实更大,但现代 JVM 对锁竞争优化较好;比起死锁或数据错乱,这点开销通常值得
什么时候该用 notify()?其实非常有限
真实项目中,能安全使用 notify() 的场景极少。它只在同时满足以下三个条件时才可考虑:
- 你**100% 确认等待队列里最多只有一个线程**(例如单消费者 + 单生产者 + 无超时重试逻辑)
- 该线程**醒来后必然能立即处理**,无需二次判断(即条件变量是独占且互斥的)
- 你愿意承担“万一有其他线程意外进入 wait(),它将永远等下去”的风险
典型反例:
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait(); // 多个消费者都可能在这里等
}
process(queue.poll());
}
// 此时用 notify() → 可能只唤醒一个,其余永远卡住
为什么 wait() 必须配 while 循环,而不是 if?
这不是风格问题,而是 JVM 规范强制要求。因为存在两种合法但不可控的唤醒可能:一是被 notify()/notifyAll() 唤醒,二是“虚假唤醒”(JVM 实现层面允许)。如果只用 if,线程一醒就往下走,极大概率操作非法状态。
立即学习“Java免费学习笔记(深入)”;
- 正确写法永远是:
while (conditionNotMet) { obj.wait(); } -
notifyAll()配合while,才能保证每个线程都自主验证条件,避免误操作 - 哪怕你只用
notify(),也必须用while—— 因为虚假唤醒不看你用哪个 notify
notify() 还是 notifyAll(),而是忘记条件检查必须是循环的、忘记唤醒后要重新竞争锁、以及高估自己对线程数量的掌控力。










