notify()和notifyAll()有什么区别?

在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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值