使用 notify/notifyAll 唤醒等待线程的区别?
notify()和notifyAll()都是用于唤醒因wait()而阻塞的线程,主要区别在于:notify()随机唤醒一个线程,适合只需单个线程处理的情况;notifyAll()唤醒所有线程,适合需要所有线程重新检查条件的场景。使用时必须在同步块内调用,避免信号丢失问题。notifyAll()更安全但可能引发性能损耗,应优先使用,除非能确保notify()的安全性。典型场景如生产者-消费者模式用n
notify() 和 notifyAll() 均用于唤醒因调用 wait() 而阻塞在对象监视器上的线程,核心区别在于唤醒线程的范围和数量,直接影响程序的并发行为和效率。
1. 核心区别对比
| 特性 | notify() | notifyAll() |
|---|---|---|
| 唤醒数量 | 仅唤醒一个等待线程(具体哪个由JVM调度决定,通常是随机的)。 | 唤醒所有等待在该对象上的线程。 |
| 竞争方式 | 被唤醒的单个线程直接参与锁竞争,成功获取锁后继续执行。 | 所有被唤醒的线程同时参与锁竞争,只有一个线程能成功获取锁,其余线程重新进入阻塞状态(WAITING)。 |
| 使用场景 | 明确知道只有一个线程能处理后续逻辑,或只需一个线程被唤醒即可。 | 不确定哪个线程能处理,或需要所有等待线程重新检查条件(避免“信号丢失”)。 |
2. 典型使用场景示例
notify() 的适用场景
当一个“生产者”线程生产了一个数据,只需唤醒一个“消费者”线程来处理即可,无需唤醒所有消费者,避免资源浪费。
// 生产者线程
synchronized (queue) {
queue.add(data);
queue.notify(); // 仅唤醒一个等待的消费者
}
notifyAll() 的适用场景
当“条件发生改变,所有等待线程都需要重新判断条件”时。例如,一个线程池的“关闭”操作,需要唤醒所有等待任务的线程,让它们检查“线程池是否已关闭”的条件并退出。
// 线程池关闭方法
synchronized (lock) {
isShutdown = true;
lock.notifyAll(); // 唤醒所有等待的工作线程
}
// 工作线程
synchronized (lock) {
while (!isShutdown && queue.isEmpty()) {
lock.wait(); // 被唤醒后重新检查条件
}
// 处理任务或退出
}
3. 关键注意事项
-
必须在同步块内调用:无论是 notify()、notifyAll() 还是 wait(),都必须在 synchronized 修饰的同步块或方法内调用,否则会抛出 IllegalMonitorStateException。
-
避免“信号丢失”:使用 notify() 时,若被唤醒的线程因某种原因(如条件不满足)再次阻塞,而其他符合条件的线程未被唤醒,会导致“信号丢失”。此时必须使用 notifyAll() 确保所有线程都有机会检查条件。
-
性能权衡:notifyAll() 会导致大量线程同时竞争锁,可能引发“惊群效应”(Thundering Herd),造成短暂的性能损耗;notify() 效率更高,但需确保逻辑正确性。
综上,优先使用 notifyAll() 除非能明确证明 notify() 是安全且高效的,尤其是在多线程等待不同条件的场景下,notifyAll() 能避免逻辑漏洞。
更多推荐


所有评论(0)