在Java多线程中,notify()和notifyAll()是Object类中用于唤醒wait()阻塞的线程的方法,二者的底层逻辑和使用场景有更细致的区别
1、唤醒的目标范围
两者的核心差异在于唤醒的线程数量,这直接由对象等待池(wair set)的机制决定:
- notify():从当前对象的等待池中随机选择一个线程唤醒 这里的随机是指唤醒那个线程由JVM实现决定的(不同的JVM可能有不同的策略,比如按照入池的一个顺序,优先级等等)
- notifyAll():唤醒当前对象池中所有的线程
2、线程唤醒后的行为
被唤醒的线程并不是立刻就执行的,而是需要重新参与锁之间的竞争,这一步是两者的共性,但细节需要注意的是:
- 无论是notify()还是notifyAll()唤醒的线程,都必须要重新获取当前对象的内置锁(synchronized锁)才能继续执行(因为wait()会释放锁,唤醒后都需要重新获得)
- 如果唤醒的是多个线程(也就是notifyAll()的场景),这些线程就会进入到一个就绪队列,通过竞争锁的方式争夺执行权,最终只有一个线程可以拿到锁盒执行,其他线程会再次阻塞(进入阻塞队列),等待下一次释放锁后重新竞争
3、对等待条件的影响
线程调用wait()通常是因为等待某个条件满足(如果生产者-消费者模型中,消费者等待队列非空)。唤醒后,线程需要重新检查条件是否真的满足,这一点在两者的使用中表现不同
- notify():只是唤醒一个线程,如果这个线程检查条件后发现不满足需求(被唤醒的队列又空了),会再次调用wait()阻塞,这个时候如果没有其他线程被唤醒,可能就会导致所有线程长期阻塞(造成一个假死)
- notifyAll():唤醒所有的线程,即使部分线程检查条件不满足而重新阻塞,至少会有一个线程在条件满足的时候执行,避免了假死的风险
4. 典型使用场景对比
-
适合
notify()的场景:所有等待线程的 “等待条件相同”,且唤醒任意一个即可完成任务。例如:生产者 - 消费者模型中,生产者添加数据后,只需唤醒一个消费者(此时队列非空,任意消费者都可处理),用notify()更高效(减少不必要的锁竞争)。 -
适合
notifyAll()的场景:等待线程的 “等待条件不同”,或需要确保所有线程都有机会检查条件。例如:多个线程等待不同的信号(如线程 A 等 “数据准备好”,线程 B 等 “缓存清空”),此时notifyAll()能保证所有线程都被唤醒并检查自己的条件,避免某类线程永远无法被触发。
5. 潜在问题与注意事项
notify()的 “饥饿风险”:由于notify()随机唤醒线程,可能导致某些线程长期不被选中,一直处于等待池(例如低优先级线程),出现 “饥饿”。notifyAll()的性能开销:唤醒所有线程会引发激烈的锁竞争,若等待线程数量多,可能导致 CPU 资源浪费(大量线程竞争锁但只有一个能执行)。- 必须在同步块中调用:两者都必须在
synchronized同步块 / 方法中调用(与wait()一致),否则会抛出IllegalMonitorStateException,因为它们需要操作当前对象的锁状态。
总结表格
| 维度 | notify() | notifyAll() |
|---|---|---|
| 唤醒数量 | 随机唤醒一个线程 | 唤醒所有等待线程 |
| 锁竞争开销 | 小(仅一个线程竞争) | 大(所有线程竞争) |
| 饥饿风险 | 可能存在 | 无(所有线程均有机会) |
| 适用场景 | 等待条件相同的线程 | 等待条件不同或需避免假死 |
核心原则:若不确定用哪个,优先考虑 notifyAll(),虽然可能有性能损耗,但能避免因逻辑漏洞导致的线程长期阻塞;若明确所有等待线程任务一致,且需优化性能,可谨慎使用 notify()。
709

被折叠的 条评论
为什么被折叠?



