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

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

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


当前回答

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

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


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

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

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


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

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

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

其他回答

我认为这取决于资源是如何生产和消费的。如果同时有5个工作对象,并且您有5个消费者对象,那么使用notifyAll()唤醒所有线程是有意义的,这样每个线程都可以处理1个工作对象。

如果您只有一个可用的工作对象,那么唤醒所有使用者对象来争夺这个对象有什么意义呢?第一个检查可用工作的线程将得到它,所有其他线程将检查并发现它们无事可做。

我在这里找到了一个很好的解释。简而言之:

The notify() method is generally used for resource pools, where there are an arbitrary number of "consumers" or "workers" that take resources, but when a resource is added to the pool, only one of the waiting consumers or workers can deal with it. The notifyAll() method is actually used in most other cases. Strictly, it is required to notify waiters of a condition that could allow multiple waiters to proceed. But this is often difficult to know. So as a general rule, if you have no particular logic for using notify(), then you should probably use notifyAll(), because it is often difficult to know exactly what threads will be waiting on a particular object and why.

我很惊讶居然没有人提到臭名昭著的“失醒”问题(谷歌it)。

基本上:

如果有多个线程在等待同一个条件, 可以让你从状态A转换到状态B的多个线程, 可以让你从状态B转换到状态A的多个线程(通常是与状态1相同的线程), 从状态A转换到状态B应该通知1中的线程。

然后,您应该使用notifyAll,除非您有可证明的保证,丢失的唤醒是不可能的。

一个常见的例子是并发FIFO队列,其中: 多个排队者(1。和3。)可以将队列从空转换为非空 多个退出队列器(2。上面)可以等待条件“队列不是空的” Empty ->非空应该通知脱队列者

您可以很容易地编写一个交叉操作,其中从一个空队列开始,2个入队者和2个出队者交互,1个入队者保持休眠状态。

这是一个可以与死锁问题相比较的问题。

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

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

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

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

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

线程有三种状态。

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

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

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

显然,notify唤醒等待集中的一个线程(any), notifyAll唤醒等待集中的所有线程。下面的讨论应能消除任何疑问。大多数时候应该使用notifyAll。如果您不确定使用哪个,那么使用notifyAll。请看下面的解释。

仔细阅读并理解。如果您有任何问题,请发邮件给我。

查看生产者/消费者(假设是一个具有两个方法的ProducerConsumer类)。IT IS BROKEN(因为它使用notify) -是的,它可能工作-甚至大多数时候,但它也可能导致死锁-我们将看到为什么:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

首先,

为什么我们需要一个while循环围绕等待?

我们需要一个while循环,以防出现这种情况:

消费者1 (C1)进入同步块,缓冲区是空的,因此C1被放入等待集(通过等待调用)。消费者2 (C2)即将进入同步方法(在上面的Y点),但生产者P1在缓冲区中放入一个对象,然后调用notify。唯一等待的线程是C1,因此它被唤醒,现在试图重新获得点X(上面)的对象锁。

Now C1 and C2 are attempting to acquire the synchronization lock. One of them (nondeterministically) is chosen and enters the method, the other is blocked (not waiting - but blocked, trying to acquire the lock on the method). Let's say C2 gets the lock first. C1 is still blocking (trying to acquire the lock at X). C2 completes the method and releases the lock. Now, C1 acquires the lock. Guess what, lucky we have a while loop, because, C1 performs the loop check (guard) and is prevented from removing a non-existent element from the buffer (C2 already got it!). If we didn't have a while, we would get an IndexArrayOutOfBoundsException as C1 tries to remove the first element from the buffer!

NOW,

为什么我们需要notifyAll?

在上面的生产者/消费者示例中,我们似乎可以使用notify。看起来是这样的,因为我们可以证明生产者和消费者的等待循环上的守卫是互斥的。也就是说,看起来我们不能让一个线程同时在put方法和get方法中等待,因为,为了使它为真,那么下面的条件必须为真:

buf.size() == 0 AND buf.size() == MAX_SIZE(假设MAX_SIZE不为0)

然而,这还不够好,我们需要使用notifyAll。让我们看看为什么……

Assume we have a buffer of size 1 (to make the example easy to follow). The following steps lead us to deadlock. Note that ANYTIME a thread is woken with notify, it can be non-deterministically selected by the JVM - that is any waiting thread can be woken. Also note that when multiple threads are blocking on entry to a method (i.e. trying to acquire a lock), the order of acquisition can be non-deterministic. Remember also that a thread can only be in one of the methods at any one time - the synchronized methods allow only one thread to be executing (i.e. holding the lock of) any (synchronized) methods in the class. If the following sequence of events occurs - deadlock results:

步骤1: - P1将1个字符放入缓冲区

步骤2: P2尝试put -检查等待循环-已经是一个字符-等待

步骤3: P3尝试put -检查等待循环-已经是一个字符-等待

步骤4: - C1尝试获取1个char C2尝试在进入get方法时获取1个字符块 C3尝试在进入get方法时获取1个字符块

步骤5: C1正在执行get方法——获取char,调用notify,退出方法 —notify唤醒P2 但是,C2在P2之前进入方法(P2必须重新获得锁),所以P2在进入put方法时阻塞 C2检查等待循环,缓冲区中没有更多字符,所以等待 C3在C2之后进入方法,但在P2之前,检查等待循环,缓冲区中没有更多字符,所以等待

步骤6: -现在:有P3, C2,和C3等待! -最后P2获取锁,在缓冲区中放入一个字符,调用notify,退出方法

第七步: P2的通知会唤醒P3(记住任何线程都可以被唤醒) P3检查等待循环条件,缓冲区中已经有一个字符,所以等待。 没有更多线程调用通知和三个线程永久挂起!

解决方案:在生产者/消费者代码(上面)中用notifyAll替换notify。