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

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

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


当前回答

notify()让您编写比notifyAll()更有效的代码。

考虑下面这段从多个并行线程执行的代码:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

可以通过使用notify()来提高效率:

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

在有大量线程的情况下,或者如果等待循环条件的计算成本很高,notify()将比notifyAll()快得多。例如,如果你有1000个线程,那么999个线程将在第一个notifyAll()之后被唤醒和评估,然后是998,然后是997,依此类推。相反,使用notify()解决方案,只会唤醒一个线程。

使用notifyAll()当你需要选择哪个线程将做下一步工作:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Finally, it's important to understand that in case of notifyAll(), the code inside synchronized blocks that have been awakened will be executed sequentially, not all at once. Let's say there are three threads waiting in the above example, and the fourth thread calls notifyAll(). All three threads will be awakened but only one will start execution and check the condition of the while loop. If the condition is true, it will call wait() again, and only then the second thread will start executing and will check its while loop condition, and so on.

其他回答

看看@xagyg发布的代码。

假设两个不同的线程正在等待两个不同的条件: 第一个线程等待buf.size() != MAX_SIZE,第二个线程等待buf.size() != 0。

假设在某一点buf.size()不等于0。JVM调用notify()而不是notifyAll(),并且通知第一个线程(而不是第二个线程)。

第一个线程被唤醒,检查buf.size(),它可能返回MAX_SIZE,然后返回等待。第二个线程没有被唤醒,继续等待,不调用get()。

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

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

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

制作人:

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

消费者:

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

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

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

在这里,所有的醒来都没有多大意义。 Wait notify和notifyall,所有这些都放在拥有对象的监视器之后。如果一个线程处于等待阶段,并且调用了notify,那么这个线程将占用该锁,此时没有其他线程可以占用该锁。所以并发访问根本不能发生。据我所知,只有在锁定对象后才能调用wait notify和notifyall。如果我错了,请指正。

摘自Effective Java博客:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

所以,我的理解是(从前面提到的博客,“Yann TM”对公认答案和Java文档的评论):

notify() : JVM awakens one of the waiting threads on this object. Thread selection is made arbitrarily without fairness. So same thread can be awakened again and again. So system's state changes but no real progress is made. Thus creating a livelock. notifyAll() : JVM awakens all threads and then all threads race for the lock on this object. Now, CPU scheduler selects a thread which acquires lock on this object. This selection process would be much better than selection by JVM. Thus, ensuring liveness.

线程有三种状态。

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

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

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