409

notify()如果有人用谷歌搜索“和之间的差异notifyAll()”,那么会弹出很多解释(撇开 javadoc 段落)。这一切都归结为被唤醒的等待线程的数量:一个 innotify()和 all in notifyAll()

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

那么notify()notifyAll()之间有什么有用的区别呢?我错过了什么吗?

4

26 回答 26

364

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

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

查看生产者/消费者(假设是具有两个方法的 ProducerConsumer 类)。它已损坏(因为它使用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 被放入等待集(通过wait调用)。消费者 2(C2)即将进入同步方法(在上面的 Y 点),但生产者 P1 将一个对象放入缓冲区,随后调用notify. 唯一等待的线程是 C1,因此它被唤醒,现在尝试在 X 点(上图)重新获取对象锁。

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

现在,

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

在上面的生产者/消费者示例中,看起来我们可以摆脱notify. 看起来是这样,因为我们可以证明生产者和消费者的等待循环中的守卫是互斥的。也就是说,看起来我们不能让线程在put方法和方法中等待get,因为,要做到这一点,必须满足以下条件:

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

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

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

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

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

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

第 4 步:
- 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 检查等待循环条件,缓冲区中已经有一个字符,所以等待。
- 没有更多的线程调用通知和三个线程永久暂停!

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

于 2010-07-06T12:58:36.130 回答
265

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

这是不正确的。 o.notifyAll()唤醒所有o.wait()调用中被阻塞的线程。线程只允许从o.wait()一个接一个返回,但每个线程都会轮到它们。


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

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

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


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

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

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

于 2008-08-31T19:25:22.790 回答
47

有用的区别:

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

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

于 2008-08-31T19:41:28.740 回答
19

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

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

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

notify() 方法一般用于资源池,其中有任意数量的“消费者”或“工作者”在占用资源,但是当资源添加到池中时,只有一个等待的消费者或工作者可以处理用它。notifyAll() 方法实际上用于大多数其他情况。严格来说,需要通知服务员可能允许多个服务员继续进行的条件。但这通常很难知道。因此,作为一般规则,如果您没有使用 notify() 的特定逻辑,那么您可能应该使用 notifyAll(),因为通常很难确切知道哪些线程将在特定对象上等待以及为什么。

于 2008-08-31T19:25:10.763 回答
12

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

Doug Lea 在他的名著中提出了一个有趣的观点:如果 anotify()Thread.interrupt()同时发生,则通知实际上可能会丢失。notifyAll()即使您付出了开销的代价(大部分时间唤醒了太多线程),如果这可能发生并具有重大影响是一个更安全的选择。

于 2009-06-17T11:41:37.257 回答
10

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

ProducerConsumer示例类

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) { }
        }
    }
}
于 2010-05-21T03:34:59.097 回答
10

简短的摘要:

总是更喜欢notifyAll()而不是notify(),除非您有一个大规模并行应用程序,其中大量线程都在做同样的事情。

解释:

notify() [...] 唤醒单个线程。因为notify()不允许您指定被唤醒的线程,所以它仅在大规模并行应用程序中有用——即具有大量线程的程序,都在做类似的工作。在这样的应用程序中,您不关心唤醒哪个线程。

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

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

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

唤醒正在此对象的监视器上等待的单个线程。[...] 选择是任意的,并由实施自行决定。

来源: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 只唤醒消费者的危险,因为任何合理实现的调度器都不会做出任意决定。相反,大多数调度程序实现至少会做出一些努力来防止饥饿。

于 2015-10-25T11:10:57.920 回答
9

来自 Java 大师本人在 Effective Java 2nd edition 中的 Joshua Bloch:

“第 69 项:优先使用并发实用程序来等待和通知”。

于 2008-09-03T14:26:12.313 回答
8

线程有三种状态。

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

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

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

于 2017-01-08T07:46:58.827 回答
6

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

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

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

制片人:

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

消费者:

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

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

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

由于通知导致的死锁

于 2017-05-25T08:17:16.157 回答
5

我很惊讶没有人提到臭名昭著的“失去唤醒”问题(谷歌搜索)。

基本上:

  1. 如果您有多个线程在同一条件下等待,并且,
  2. 可以使您从状态 A 转换到状态 B 的多个线程,并且,
  3. 可以使您从状态 B 转换到状态 A 的多个线程(通常与 1 中的线程相同),并且,
  4. 从状态 A 到 B 的转换应该通知 1 中的线程。

那么你应该使用 notifyAll 除非你有可证明的保证丢失唤醒是不可能的。

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

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

这是一个可以与死锁问题相媲美的问题。

于 2012-09-15T16:50:48.943 回答
5

这是一个更简单的解释:

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

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

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

于 2013-06-25T00:18:48.853 回答
4

notify()将唤醒一个线程,同时notifyAll()将唤醒所有线程。据我所知,没有中间立场。但是如果你不确定notify()你的线程会做什么,使用notifyAll(). 每次都像魅力一样工作。

于 2008-08-31T19:19:21.713 回答
4

据我所知,以上所有答案都是正确的,所以我要告诉你一些别的事情。对于生产代码,您确实应该使用 java.util.concurrent 中的类。在 java 的并发领域,他们几乎没有什么不能为你做的。

于 2008-08-31T19:50:58.530 回答
4

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 个线程,则将在第一个notifyAll()998 和 997 之后唤醒和评估 999 个线程,以此类推。相反,使用该notify()解决方案,只会唤醒一个线程。

notifyAll()当您需要选择接下来执行工作的线程时 使用:

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循环条件,依此类推。

于 2013-06-17T23:23:24.293 回答
4

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

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

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

于 2015-10-25T12:56:57.500 回答
3

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

第一点,是Notify还是NotifyAll?

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

如果两个线程 A 和 B 正在等待同一条件队列的不同条件谓词并调用 notify,则由 JVM 通知哪个线程 JVM。

现在,如果通知是针对线程 A 的,而 JVM 通知了线程 B,那么线程 B 将唤醒并看到此通知没有用,因此它将再次等待。线程 A 永远不会知道这个错过的信号,并且有人劫持了它的通知。

因此,调用 notifyAll 将解决此问题,但同样会影响性能,因为它会通知所有线程并且所有线程将竞争相同的锁,并且会涉及上下文切换,因此会加载 CPU。但是我们应该只关心性能是否正确,如果它的行为本身不正确,那么性能是没有用的。

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

于 2013-09-06T05:34:22.730 回答
3

取自关于 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 唤醒该对象上的一个等待线程。线程选择是任意的,没有公平性。因此可以一次又一次地唤醒同一线程。因此系统的状态发生了变化,但没有取得真正的进展。从而创建了一个活锁
  • notifyAll() :JVM 唤醒所有线程,然后所有线程争用该对象上的锁。现在,CPU 调度程序选择一个线程来获取该对象的锁定。这个选择过程会比 JVM 的选择要好得多。因此,保证了活力。
于 2017-09-25T21:47:45.387 回答
2

看看@xagyg 发布的代码。

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

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

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

于 2012-03-26T21:45:56.690 回答
1

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

于 2014-07-22T18:25:48.640 回答
1

总结上面优秀的详细解释,并且以我能想到的最简单的方式,这是由于JVM内置监视器的限制,1)在整个同步单元(块或对象)上获取和2)不区分正在等待/通知的具体情况。

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

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

因此,只有在保证任何等待线程在被选中时允许取得进展的情况下,才可以安全地使用 notify(),这通常在同一监视器中的所有线程只检查一个相同的条件时得到满足——这是相当罕见的实际应用中的案例。

于 2015-10-11T15:46:20.247 回答
0

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

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

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

于 2011-12-05T18:49:15.600 回答
0

当您调用“对象”的 wait() 时(期望获得对象锁),实习生这将释放该对象上的锁并帮助其他线程锁定该“对象”,在这种情况下会有超过 1 个线程在等待“资源/对象”(考虑到其他线程也对上述同一个对象发出了等待,并且沿途会有一个线程填充资源/对象并调用 notify/notifyAll)。

在这里,当您发出同一对象的通知时(来自进程/代码的同一/另一侧),这将释放一个阻塞和等待的单线程(不是所有等待的线程——这个释放的线程将被 JVM 线程拾取调度器和对象上的所有锁获取过程与常规相同)。

如果您只有一个线程将共享/处理此对象,则可以在等待通知实现中单独使用 notify() 方法。

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

现在我正在查看当我们在对象上发出 notify() 时 jvm 是如何识别和中断等待线程的......

于 2016-04-21T18:32:01.033 回答
0

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

回到问题:总而言之,今天的最佳实践是在所有情况下避免由于丢失唤醒问题而导致的 notify()。任何不理解这一点的人都不应被允许编写关键任务并发代码。如果您担心羊群问题,实现一次唤醒一个线程的一种安全方法是:

  1. 为等待线程建立显式等待队列;
  2. 让队列中的每个线程等待其前任;
  3. 完成后让每个线程调用 notifyAll()。

或者您可以使用已经实现此功能的 Java.util.concurrent.*。

于 2017-01-28T14:03:05.903 回答
0

等待队列和阻塞队列

您可以假设有两种队列与每个锁定对象相关联。一种是包含等待监控锁的线程的阻塞队列,另一种是包含等待通知的线程的等待队列。(当他们调用时,线程将被放入等待队列Object.wait)。

每次锁可用时,调度程序从阻塞队列中选择一个线程来执行。

调用时notify,等待队列中只有一个线程进入阻塞队列争夺锁,而notifyAll等待队列中的所有线程都进入阻塞队列。

现在你能看出区别了吗?
虽然在这两种情况下只会有一个线程被执行,但是notifyAll,其他线程仍然会执行更改(因为它们在阻塞队列中),即使它们未能争用锁。

一些指导方针

我基本上建议一直使用notifyAll,尽管可能会有一点性能损失。
并且notify仅在以下情况下使用:

  1. 任何唤醒的线程都可以使程序继续进行。
  2. 性能很重要。

例如:
@xagyg 的答案给出了一个notify会导致死锁的例子。在他的示例中,生产者和消费者都与同一个锁对象相关。因此,当生产者调用时notify,可以通知生产者或消费者。但是如果生产者被唤醒,它就不能让程序继续进行,因为缓冲区已经满了。所以就会发生死锁。
有两种方法可以解决它:

  1. notifyALl按照@xagyg 的建议使用。
  2. 使procuder和consumer关联不同的锁对象,procuder只能唤醒consumer,consumer只能唤醒producer。在这种情况下,无论哪个消费者被唤醒,它都可以消费缓冲区并使程序继续进行。
于 2020-10-18T03:38:21.077 回答
-2

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

于 2009-12-14T08:17:21.180 回答