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

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

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


当前回答

当你调用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状态,那么其他被唤醒的线程将返回等待状态。

线程有三种状态。

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将获得它(假设没有其他线程竞争该监视器)并开始执行。

我认为这取决于资源是如何生产和消费的。如果同时有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.

这里有一个例子。运行它。然后将notifyAll()中的一个更改为notify(),看看会发生什么。

ProducerConsumerExample类

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Dropbox类

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

消费阶层

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

生产类

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}