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

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

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


当前回答

虽然上面有一些可靠的答案,但我对我读到的困惑和误解的数量感到惊讶。这可能证明了应该尽可能多地使用java.util.concurrent,而不是尝试编写自己的坏并发代码。

回到问题:总结一下,目前的最佳实践是在所有情况下避免notify(),因为会出现丢失唤醒的问题。任何不理解这一点的人都不应该被允许编写关键任务并发代码。如果你担心羊群问题,实现一次唤醒一个线程的安全方法是:

为等待线程构建一个显式的等待队列; 让队列中的每个线程等待它的前一个线程; 完成后让每个线程调用notifyAll()。

或者你可以使用Java.util.concurrent。*,它们已经实现了这一点。

其他回答

Notify将只通知处于等待状态的一个线程,而Notify all将通知处于等待状态的所有线程,现在所有被通知的线程和所有被阻塞的线程都有资格获得锁,其中只有一个线程将获得锁,所有其他线程(包括之前处于等待状态的线程)将处于阻塞状态。

线程有三种状态。

WAIT -线程没有使用任何CPU周期 BLOCKED -线程在试图获取监视器时被阻塞。它可能仍在使用CPU周期 RUNNING -线程正在运行。

现在,当调用notify()时,JVM选择一个线程并将其移动到BLOCKED状态,从而将其移动到RUNNING状态,因为没有竞争监视器对象。

当调用notifyAll()时,JVM选取所有线程并将它们移到BLOCKED状态。所有这些线程都将优先获得对象的锁。能够首先获取监视器的线程将能够首先进入RUNNING状态,依此类推。

这里有一个简单的解释:

您是正确的,无论使用notify()还是notifyAll(),直接结果都是恰好有另一个线程获得监视器并开始执行。(假设一些线程实际上在wait()上阻塞了这个对象,其他不相关的线程并没有占用所有可用的内核,等等。)

假设线程A、B和C正在等待这个对象,线程A得到监视器。区别在于当A释放监视器时发生了什么。如果你使用notify(),那么B和C仍然被阻塞在wait()中:它们不是在监视器上等待,而是在等待通知。当A释放监视器时,B和C仍然在那里等待notify()。

如果使用notifyAll(),则B和C都已进入“等待通知”状态,都在等待获取监视器。当A释放监视器时,B或C将获得它(假设没有其他线程竞争该监视器)并开始执行。

当你调用wait()的“对象”(期望对象锁)、实习生这将释放锁,物体和帮助的其他线程锁在这个“对象”,在这种情况下,将会有超过1线程等待“资源/对象”(考虑到其他线程也发布了等待上面相同的对象,将会有一个线程的方式填补资源/对象并调用通知/ notifyAll)。

在这里,当您(从进程/代码的同一/另一端)发出同一对象的通知时,这将释放一个阻塞和等待的单个线程(不是所有等待的线程——这个释放的线程将由JVM thread Scheduler挑选,对象上的所有锁获取进程与常规进程相同)。

如果只有一个线程共享/处理这个对象,那么可以在wait-notify实现中单独使用notify()方法。

如果您处于基于业务逻辑的多个线程对资源/对象进行读写的情况,那么您应该使用notifyAll()

现在我正在寻找JVM是如何识别和打破等待线程时,我们发出通知()在一个对象…

但是(如果我正确理解了这些方法之间的区别),总是只选择一个线程进行进一步的监视采集。

这是不对的。o.notifyAll()唤醒在o.wait()调用中阻塞的所有线程。线程只允许一个接一个地从o.wait()返回,但它们将轮流返回。


简单地说,这取决于线程等待通知的原因。您是想告诉其中一个等待线程发生了什么,还是想同时告诉所有等待线程?

在某些情况下,所有等待线程在等待结束后都可以采取有用的操作。一个例子是一组等待某个任务完成的线程;一旦任务完成,所有等待的线程都可以继续它们的业务。在这种情况下,您可以使用notifyAll()同时唤醒所有等待的线程。

另一种情况,例如互斥锁,只有一个等待线程在被通知后可以做一些有用的事情(在这种情况下获得锁)。在这种情况下,您更愿意使用notify()。如果实现得当,您也可以在这种情况下使用notifyAll(),但是您将不必要地唤醒无法做任何事情的线程。


在很多情况下,等待条件的代码会被写成循环:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

这样,如果一个o.notifyAll()调用唤醒了多个等待线程,并且第一个从o.wait() make返回的线程将条件保持在false状态,那么其他被唤醒的线程将返回等待状态。