应优先使用 notifyall()。notify() 随机唤醒单个线程,易导致条件不匹配和信号丢失;notifyall() 唤醒所有等待线程,由它们重新竞争锁并检查条件,更安全可靠,且现代 jvm 已优化其性能。

Java 中 notify() 和 notifyAll() 都用于唤醒因 wait() 进入等待状态的线程,但它们在行为、安全性和适用场景上有本质区别。简单说:除非你百分百确定只有一个线程在等,且逻辑绝对封闭,否则该用 notifyAll()。
唤醒范围与随机性
notify() 从当前对象的 wait set 中随机选一个线程唤醒,无法控制目标——不是按等待顺序,也不是按优先级,完全由 JVM 调度器决定。哪怕你写了“先等的先醒”,JVM 也不保证。
notifyAll() 则唤醒所有在该对象上等待的线程,让它们全部重新竞争锁并检查条件。
- 调用
notify()时若没有线程在等,操作无效;而notifyAll()同样无效,但不会掩盖逻辑隐患 - 被唤醒的线程都必须重新获取锁才能继续执行,不是一醒就跑
- 即使
notifyAll()唤醒了 10 个线程,最终也只有一个能拿到锁执行,其余会再次阻塞(进入 entry set)
为什么 notify 容易出错
问题不在“唤醒一个”本身,而在“唤醒谁”不可控,容易导致条件不匹配:
- 多个角色共用一把锁时(如生产者和消费者),
notify()可能唤醒了一个生产者,但它发现缓冲区已满,又立刻wait()回去,而真正该醒的消费者却被漏掉了 - 不同线程等待不同子条件(比如线程 A 等
count > 5,线程 B 等count % 2 == 0),notify()唤醒 B 时count是 7,B 检查失败后继续等待,A 却永远没机会被叫醒 - 框架或监控代码悄悄引入额外等待线程(如 Spring AOP、日志拦截器),你以为“只有 1 个在等”,其实 wait set 里已有 3 个
notifyAll 更安全,也没你想的那么重
很多人担心 notifyAll() 效率低,但现代 JVM(如 HotSpot)做了优化:它只是把等待线程从 wait set 移到 entry set,并不触发完整调度流程。真正耗资源的是锁竞争,不是唤醒动作。
- JDK 所有标准并发容器(
ArrayBlockingQueue、LinkedBlockingQueue、ReentrantLock的Condition)全用notifyAll() - 被唤醒的线程都会重新检查守卫条件(
while (!condition) wait();),天然过滤掉不满足条件的线程 - 它防住了虚假唤醒(spurious wakeup)和信号丢失,是工程上的兜底选择
正确写法是铁律,和选哪个无关
无论用 notify() 还是 notifyAll(),以下两条必须遵守:
-
wait()、notify()、notifyAll()必须在同一个对象的synchronized块内调用 -
wait()必须写在while循环里,绝不能用if—— 因为唤醒后条件可能已变,或根本就是虚假唤醒
例如:
synchronized (lock) {<br> while (!dataReady) {<br> lock.wait();<br> }<br> // 处理 data<br>}
通知端修改完条件后,直接 lock.notifyAll() 即可,不用纠结“该不该只叫一个”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











