关于多线程:Java:再次遍历notify()与notifyAll()

Java: notify() vs. notifyAll() all over again

如果一个Google搜索" notify()notifyAll()之间的差异",则会弹出很多说明(将javadoc段落分开)。 一切都归结为正在唤醒的等待线程数:notify()中一个,所有notifyAll()中。

但是(如果我确实正确理解了这两种方法之间的区别),总是仅选择一个线程来进行进一步的监视器获取; 在第一种情况下,由VM选择,在第二种情况下,由系统线程调度程序选择。 程序员不知道它们的确切选择过程(一般情况下)。

然后notify()和notifyAll()的有用区别是什么? 我想念什么吗?


显然,notify唤醒(任何)等待集中的一个线程,notifyAll唤醒所有等待集中的线程。以下讨论应消除任何疑问。 notifyAll应该在大多数时间使用。如果不确定使用哪个,请使用notifyAll。请参阅以下说明。

仔细阅读并理解。如有任何疑问,请给我发送电子邮件。

查看生产者/消费者(假设是一个ProducerConsumer类,具有两个方法)。它已损坏(因为它使用notify)-是的,它可能可以工作-即使在大多数情况下,也可能导致死锁-我们将看到原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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放入等待集中(通过wait调用)。使用者2(C2)将进入同步方法(在上面的Y点),但是生产者P1将对象放入缓冲区,然后调用notify。唯一等待的线程是C1,因此它被唤醒,现在尝试重新获取X点(上方)的对象锁。

现在,C1和C2正在尝试获取同步锁。选择其中一个(不确定)并进入方法,另一个被阻止(不等待-但被阻止,试图获取方法的锁)。假设C2首先获得了锁。 C1仍在阻塞(试图获取X的锁)。 C2完成该方法并释放锁。现在,C1获取锁。猜猜是什么,幸运的是,我们有一个while循环,因为C1执行循环检查(保护)并被阻止从缓冲区中删除不存在的元素(C2已经得到了!)。如果我们没有while,我们将得到一个IndexArrayOutOfBoundsException,因为C1试图从缓冲区中删除第一个元素!

现在,

好的,现在为什么我们需要notifyAll?

在上面的生产者/消费者示例中,看起来我们可以摆脱notify了。看来是这样,因为我们可以证明生产者和消费者等待循环中的保护措施是互斥的。也就是说,看起来我们不能在put方法和get方法中都有线程在等待,因为要使它成立,则以下各项必须成立:

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

但是,这还不够好,我们需要使用notifyAll。让我们看看为什么...

假设我们有一个大小为1的缓冲区(使该示例易于理解)。以下步骤导致我们陷入僵局。请注意,任何时候使用notify唤醒线程时,JVM都可以不确定地选择该线程-即可以唤醒任何等待的线程。还应注意,当多个线程在进入某个方法时处于阻塞状态(即尝试获取锁)时,获取的顺序可能是不确定的。还要记住,线程在任何时候都只能位于其中一个方法中-同步方法仅允许一个线程正在执行(即,持有该类中任何(同步)方法的锁)。如果发生以下事件序列,则会导致死锁:

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

第2步:
-P2尝试put-检查等待循环-已经是char-等待

步骤3:
-P3尝试put-检查等待循环-已经是char-等待

第四步:
-C1尝试获取1个字符
-C2尝试获取1个字符-进入get方法的块
-C3尝试获取1个字符-进入get方法的块

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

步骤6:
-现在:正在等待P3,C2和C3!
-最后,P2获取锁,将char放入缓冲区,调用notify,退出方法

步骤7:
-P2的通知唤醒P3(请记住可以唤醒任何线程)
-P3检查等待循环条件,缓冲区中已经有一个字符,因此等待。
-没有更多的消息可以通知,并且三个线程将永久暂停!

解决方案:在生产者/消费者代码中,用notifyAll替换notify

好。


However (if I do understand the difference between these methods right), only one thread is always selected for further monitor acquisition.

那是不对的。 o.notifyAll()唤醒o.wait()调用中阻塞的所有线程。线程只能从o.wait()一对一地返回,但它们各自都会轮到。

简而言之,这取决于您的线程为何等待被通知。您是否要告诉一个正在等待的线程发生了什么,还是要同时告诉所有这些线程?

在某些情况下,等待完成后,所有等待线程都可以采取有用的措施。一个示例是一组等待某个任务完成的线程。任务完成后,所有等待的线程都可以继续其业务。在这种情况下,您可以使用notifyAll()来同时唤醒所有等待的线程。

另一种情况,例如互斥锁定,只有一个等待线程在得到通知后可以做一些有用的事情(在这种情况下,获取锁定)。在这种情况下,您宁愿使用notify()。正确实现后,在这种情况下,您也可以使用notifyAll(),但是不必要地唤醒了无法执行任何操作的线程。

在许多情况下,等待条件的代码将被编写为循环:

1
2
3
4
5
6
synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

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


有用的区别:

  • 如果所有等待线程都可以互换(唤醒顺序无关紧要),或者只有一个等待线程,请使用notify()。一个常见的示例是用于执行队列中作业的线程池-添加作业后,通知其中一个线程唤醒,执行下一个作业并返回睡眠状态。

  • 在其他情况下,等待线程可能有不同的用途,并且应该能够并行运行,请使用notifyAll()。一个示例是对共享资源的维护操作,其中多个线程在访问该资源之前正在等待操作完成。


我认为这取决于资源的生产和消费方式。如果一次有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.


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

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


来自Java专家Joshua Bloch的《有效Java第二版》:

"项目69:建议并发实用程序等待并通知"。


这是一个例子。运行。然后将notifyAll()之一更改为notify(),看看会发生什么。

ProducerConsumerExample类

1
2
3
4
5
6
7
8
9
10
11
12
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类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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();
    }
}

消费阶层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) { }
        }
    }
}

生产者阶层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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) { }
        }
    }
}

简短的摘要:

除非您有一个大型并行应用程序,其中大量线程都在执行相同的操作,否则始终始终使用notifyAll()而不是notify()。

说明:

notify() [...] wakes up a single
thread. Because notify() doesn't allow you to specify the thread that is
woken up, it is useful only in massively parallel applications — that
is, programs with a large number of threads, all doing similar chores.
In such an application, you don't care which thread gets woken up.

来源:https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

在上述情况下,将notify()与notifyAll()进行比较:一个大型并行应用程序,其中线程在做同样的事情。如果在这种情况下调用notifyAll(),则notifyAll()将导致大量线程的唤醒(即调度),其中许多线程不必要地唤醒(因为只有一个线程可以实际继续进行,即将被授予执行权的线程)。监视对象的wait(),notify()或notifyAll()被调用),因此浪费了计算资源。

因此,如果您没有大量线程同时执行相同操作的应用程序,则优先使用notifyAll()而不是notify()。为什么?因为,正如其他用户在此论坛中已经回答的那样,notify()

wakes up a single thread that is waiting on this object's monitor. [...] The
choice is arbitrary and occurs at the discretion of the
implementation.

来源:Java SE8 API(https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

想象一下,您有一个生产者消费者应用程序,其中消费者已经准备就绪(即wait()ing)可以消费,生产者已经准备就绪(即wait()ing)可以生产并且项目队列(要生产/消费)为空。在那种情况下,notify()可能只唤醒使用者,而不会唤醒生产者,因为唤醒者的选择是任意的。尽管生产者和消费者分别准备生产和消费,但生产者消费者周期不会取得任何进展。相反,消费者被唤醒(即保持wait()状态),因为该物品为空而不会将其从队列中取出,并且notify()的另一个消费者继续进行。

相反,notifyAll()会同时唤醒生产者和消费者。计划谁的选择取决于计划程序。当然,根据调度程序的实现,调度程序也可能只调度使用者(例如,如果您将使用者线程分配为非常高的优先级)。但是,这里的假设是,调度程序仅调度使用方的危险性低于JVM仅唤醒使用方的危险性,因为任何合理实现的调度器都不会做出任意决定。而是,大多数调度程序实现都至少做了一些努力来防止饥饿。


令我惊讶的是,没有人提到臭名昭著的"丢失的唤醒"问题(用Google搜索)。

基本上:

  • 如果您有多个线程在相同条件下等待,并且,
  • 多个线程可以使您从状态A转换为状态B,并且
  • 多个线程可以使您从状态B转换到状态A(通常与1中的线程相同),并且,
  • 从状态A转换为B时应通知1中的线程。
  • 然后,除非可以证明无法丢失唤醒,否则应使用notifyAll。

    一个常见的示例是并发FIFO队列,其中:
    多个队列(上面的1.和3.)可以将您的队列从空过渡到非空
    多个出队列(上面的2.)可以等待条件"队列不为空"
    空->非空应通知出队者

    您可以轻松地编写一个操作交错,其中从一个空队列开始,有2个入队者和2个出队者进行交互,并且1个入队者将保持睡眠状态。

    这个问题可以说与死锁问题相当。


    线程有三种状态。

  • 等待-线程未使用任何CPU周期
  • BLOCKED-试图获取监视器的线程被阻塞。它可能仍在使用CPU周期
  • RUNNING-线程正在运行。
  • 现在,当调用notify()时,JVM将选择一个线程并将其移至BLOCKED状态,从而移至RUNNING状态,因为没有与监视对象的竞争。

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


    我希望这将消除一些疑问。

    notify():notify()方法唤醒一个正在等待的线程
    锁(在该锁上调用wait()的第一个线程)。

    notifyAll():notifyAll()方法唤醒所有等待锁定的线程。 JVM从等待锁定的线程列表中选择一个线程并唤醒
    该线程了。

    在单个线程等待锁定的情况下,notify()和notifyAll()之间没有显着差异。但是,当notify()和notifyAll()中有多个线程在等待锁时,唤醒的确切线程在JVM的控制之下,并且您无法以编程方式控制唤醒特定线程。

    乍一看,似乎最好只调用notify()来唤醒一个线程。似乎没有必要唤醒所有线程。但是,notify()的问题在于,唤醒的线程可能不是适合唤醒的线程(线程可能正在等待其他条件,或者该线程仍然不满足该条件,等等)。在那种情况下,notify()可能会丢失,并且其他线程也不会唤醒,从而可能导致某种类型的死锁(通知丢失,并且所有其他线程一直在等待通知)。

    为避免此问题,当有多个线程在等待锁(或有多个等待条件)时,最好总是调用notifyAll()。 notifyAll()方法唤醒所有线程,因此效率不高。但是,在实际应用中,这种性能损失可以忽略不计。


    就我所知,以上所有答案都是正确的,所以我将告诉您其他事情。对于生产代码,您确实应该使用java.util.concurrent中的类。在Java的并发方面,它们对您无能为力。


    notify()将唤醒一个线程,而notifyAll()将唤醒所有线程。据我所知,没有中间立场。但是,如果不确定notify()对线程有什么作用,请使用notifyAll()。每次都像魅力一样。


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

    请考虑以下从多个并行线程执行的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    synchronized(this) {
        while(busy) // a loop is necessary here
            wait();
        busy = true;
    }
    ...
    synchronized(this) {
        busy = false;
        notifyAll();
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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个线程,则将在第一个notifyAll(),然后的998,然后的997等之后唤醒和评估999个线程。相反,使用notify()解决方案,将仅唤醒一个线程。

    当需要选择下一步执行工作的线程时,请使用notifyAll()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    synchronized(this) {
        while(idx != last+1)  // wait until it's my turn
            wait();
    }
    ...
    synchronized(this) {
        last = idx;
        notifyAll();
    }

    最后,重要的是要理解,在notifyAll()的情况下,已唤醒的synchronized块中的代码将顺序执行,而不是一次执行。假设在上面的示例中有三个线程在等待,第四个线程调用notifyAll()。所有三个线程将被唤醒,但是只有一个线程将开始执行并检查while循环的条件。如果条件是true,它将再次调用wait(),只有这样,第二个线程才会开始执行并检查其while循环条件,依此类推。


    这是一个更简单的解释:

    您是正确的,无论您使用notify()还是notifyAll(),直接的结果就是恰好有另一个线程将获取该监视器并开始执行。 (假设实际上有一些线程在此对象的wait()上被阻塞,其他不相关的线程没有吸收所有可用的内核,等等。)影响是稍后出现的。

    假设线程A,B和C在等待该对象,并且线程A获取了监视器。区别在于,一旦A释放监视器,将会发生什么。如果您使用notify(),那么B和C仍然在wait()中被阻塞:它们没有在监视器上等待,而是在等待被通知。当A释放监视器时,B和C仍将坐在那里,等待notify()。

    如果您使用notifyAll(),则B和C都已超过"等待通知"状态,并且都在等待获取监视器。当A释放监视器时,B或C都将获取它(假定没有其他线程在争夺该监视器)并开始执行。


    该答案是xagyg出色回答(包括eran的评论)的图形化重写和简化。

    即使每种产品都面向单个消费者,为什么还要使用notifyAll?

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

    制片人:

    1
    2
    3
    4
    5
    while (!empty) {
       wait() // on full
    }
    put()
    notify()

    消费者:

    1
    2
    3
    4
    5
    while (empty) {
       wait() // on empty
    }
    take()
    notify()

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

    每个通知都标有正在唤醒的线程。

    deadlock due to notify


    取自有效Java博客:

    1
    2
    3
    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唤醒此对象上的一个等待线程。 线程选择是任意选择的,没有公平性。 因此,可以一次又一次唤醒同一线程。 因此,系统的状态发生了变化,但是没有取得真正的进展。 从而创建了一个活锁。
    • notifyAll():JVM唤醒所有线程,然后所有线程争夺该对象的锁定。 现在,CPU调度程序选择一个线程来获取对该对象的锁定。 该选择过程将比通过JVM进行选择要好得多。 因此,确保了生活。

    我想提一下Java Concurrency in Practice中的解释:

    第一点,是Notify还是NotifyAll?

    1
    It will be NotifyAll, and reason is that it will save from signall hijacking.

    If two threads A and B are waiting on different condition predicates
    of same condition queue and notify is called, then it is upto JVM to
    which thread JVM will notify.

    Now if notify was meant for thread A and JVM notified thread B, then
    thread B will wake up and see that this notification is not useful so
    it will wait again. And Thread A will never come to know about this
    missed signal and someone hijacked it's notification.

    So, calling notifyAll will resolve this issue, but again it will have
    performance impact as it will notify all threads and all threads will
    compete for same lock and it will involve context switch and hence
    load on CPU. But we should care about performance only if it is
    behaving correctly, if it's behavior itself is not correct then
    performance is of no use.

    可以使用jdk 5中提供的显式锁定Lock的Condition对象解决此问题,因为它为每个条件谓词提供了不同的等待时间。在这里它将正常运行,并且不会出现性能问题,因为它将调用signal并确保只有一个线程在等待该条件


    看一下@xagyg发布的代码。

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

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

    第一个线程被唤醒,检查可能返回MAX_SIZEbuf.size(),然后返回等待状态。第二个线程未唤醒,继续等待并且不调用get()


    notify()唤醒同一对象上调用wait()的第一个线程。

    notifyAll()唤醒同一对象上所有调用wait()的线程。

    优先级最高的线程将首先运行。


    notify将仅通知一个处于等待状态的线程,而notify all将通知所有处于等待状态的线程,现在所有已通知的线程和所有被阻止的线程都可以使用该锁,只有其中一个将获得该锁,并且所有其他人(包括较早处于等待状态的人)将处于阻止状态。


    总结上述出色的详细说明,并且以我能想到的最简单的方式,这是由于JVM内置监视器的局限性,其中1)是在整个同步单元(块或对象)上获取的,而2)不区分正在等待/通知的特定条件。

    这意味着,如果多个线程正在等待不同的条件,并且使用notify(),则所选线程可能不是在新满足的条件下会取得进展的线程-导致该线程(以及其他可能仍在等待的线程满足条件等。)无法取得进展,最终导致饥饿或程序中断。

    相反,notifyAll()允许所有等待线程最终重新获取该锁并检查它们各自的条件,从而最终允许取得进展。

    因此,仅在保证有任何等待线程允许进行选择的情况下才可以安全地使用notify(),通常,当同一监视器内的所有线程仅检查一个相同条件时,通常会满足该条件(相当少见)在现实世界中的应用案例。


    notify()-从对象的等待集中选择一个随机线程,并将其置于BLOCKED状态。对象的等待集中的其余线程仍处于WAITING状态。

    notifyAll()-将所有线程从对象的等待集中移到BLOCKED状态。使用notifyAll()后,共享对象的等待集中没有线程剩余,因为所有线程现在都处于BLOCKED状态而不是WAITING状态。

    BLOCKED-阻止获取锁。
    WAITING-等待通知(或为加入完成而阻塞)。


    当您调用"对象"的wait()(希望获得对象锁)时,实习生将释放该对象上的锁,并帮助其他线程对该对象进行锁定,在这种情况下,超过1个线程在等待"资源/对象"(考虑其他线程也在上面的同一对象上发出了等待,然后将有一个线程填充资源/对象并调用notify / notifyAll)。

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

    如果只有一个线程将在此对象上共享/工作,则可以在wait-notify实现中单独使用notify()方法。

    如果您处于根据业务逻辑在资源/对象上读写多个线程的情况下,则应使用notifyAll()

    现在我正在寻找当我们在对象上发出notify()时,jvm到底是如何识别并打破等待线程的。


    尽管上面有一些可靠的答案,但我对我所读到的许多困惑和误解感到惊讶。这可能证明了这样一种想法,即应该尽可能多地使用java.util.concurrent,而不是尝试编写自己的损坏的并发代码。
    回到问题:总之,由于丢失的唤醒问题,当今的最佳实践是在所有情况下都避免使用notify()。不了解这一点的任何人都不应编写关键任务并发代码。如果您担心放牧问题,一次唤醒一个线程的一种安全方法是:
    1.为等待的线程建立一个明确的等待队列;
    2.让队列中的每个线程等待它的前任;
    3.完成后,让每个线程调用notifyAll()。
    或者,您可以使用已经实现了此功能的Java.util.concurrent。*。


    唤醒所有人在这里没有多大意义。
    等待notify和notifyall,所有这些都放在拥有对象的监视器之后。如果某个线程处于等待阶段并调用notify,则该线程将占用该锁,并且此时其他线程都无法占用该锁。因此并发访问根本不会发生。据我所知,任何等待通知和notifyall的调用只有在锁定对象之后才能进行。如果我错了请纠正我。


    推荐阅读