如果一个人在谷歌上搜索“notify()和notifyAll()之间的区别”,那么会跳出很多解释(撇开javadoc段落)。这都归结于被唤醒的等待线程的数量:notify()中有一个,notifyAll()中有所有线程。

然而(如果我确实理解了这些方法之间的区别),只有一个线程总是被选择用于进一步的监视采集;第一种情况是VM选择的线程,第二种情况是系统线程调度程序选择的线程。程序员不知道它们的确切选择过程(在一般情况下)。

那么notify()和notifyAll()之间有什么有用的区别呢?我遗漏了什么吗?


当前回答

摘自Java大师Joshua Bloch在Effective Java第二版中的文章:

“第69项:选择并发实用程序而不是等待和通知”。

其他回答

注意,对于并发实用程序,您还可以在signal()和signalAll()之间进行选择,因为在那里调用了这些方法。因此,即使使用java.util.concurrent,这个问题仍然有效。

Doug Lea在他著名的书中提出了一个有趣的观点:如果notify()和Thread.interrupt()同时发生,通知实际上可能会丢失。如果可能发生这种情况,并且有显著的影响,notifyAll()是一个更安全的选择,即使您付出了开销的代价(大多数时间唤醒太多线程)。

notify()将唤醒一个线程,而notifyAll()将唤醒所有线程。据我所知,没有中间立场。但是如果你不确定notify()会对你的线程做什么,使用notifyAll()。每次都很灵验。

等待队列和阻塞队列

您可以假设与每个锁对象关联的队列有两种类型。一个是阻塞队列,包含等待监控器锁的线程,另一个是等待队列,包含等待通知的线程。(线程调用Object.wait时将被放入等待队列)。

每次锁可用时,调度器从阻塞队列中选择一个线程执行。

当调用notify时,等待队列中只有一个线程被放入阻塞队列中争夺锁,而notifyAll将等待队列中的所有线程放入阻塞队列中。

现在你能看出区别了吗? 尽管在这两种情况下,只有一个线程被执行,但使用notifyAll,其他线程仍然得到一个要执行的更改(因为它们在阻塞队列中),即使它们未能争用锁。

一些指导原则

我基本上建议一直使用notifyAll,尽管可能会有一点性能损失。 仅在以下情况下使用notify:

任何被唤醒的线程都可以使程序继续运行。 性能很重要。

例如: @xagyg的回答给出了一个通知会导致死锁的例子。在他的例子中,生产者和消费者都与同一个锁对象相关。因此,当生产者调用notify时,可以通知生产者或消费者。但是,如果一个生产者被唤醒,它就不能使程序继续进行,因为缓冲区已经满了。因此发生了死锁。 有两种解决方法:

使用@xagyg建议的notifyALl。 使生产者和消费者关联不同的锁对象,并且生产者只能唤醒消费者,消费者只能唤醒生产者。在这种情况下,无论唤醒哪个消费者,它都可以消费缓冲区并使程序继续进行。

据我所知,以上所有答案都是正确的,所以我要告诉你一些其他的事情。对于生产代码,您确实应该使用java.util.concurrent中的类。在java的并发性方面,它们几乎没有不能为你做的事情。

这个答案是xagyg的优秀答案的图形重写和简化,包括eran的评论。

为什么要使用notifyAll,即使每个产品都是针对单个消费者的?

考虑生产者和消费者,简化如下。

制作人:

while (!empty) {
   wait() // on full
}
put()
notify()

消费者:

while (empty) {
   wait() // on empty
}
take()
notify()

假设2个生产者和2个消费者共享一个大小为1的缓冲区。下图描述了一个导致死锁的场景,如果所有线程都使用notifyAll,就可以避免死锁。

每个通知都被标记为被唤醒的线程。