如果有人在谷歌上搜索“两者之间的区别 notify()notifyAll()" 然后会弹出很多解释(除了 javadoc 段落)。这一切都归结为被唤醒的等待线程的数量:一进 notify() 和所有在 notifyAll().

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

什么是 有用 之间的区别 通知()通知全部() 然后?我错过了什么吗?

有帮助吗?

解决方案

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

这是不正确的。 o.notifyAll() 醒来 全部 被阻塞的线程数 o.wait() 来电。线程只允许从以下位置返回 o.wait() 一个接一个,但他们每个 将要 轮到他们了。


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

在某些情况下,一旦等待完成,所有等待线程都可以采取有用的操作。一个例子是一组线程等待某个任务完成;一旦任务完成,所有等待线程就可以继续其业务。在这种情况下你会使用 通知全部() 同时唤醒所有等待线程。

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


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

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

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

其他提示

清楚地, 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)即将进入synchronized方法(上面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 的缓冲区(为了使示例易于理解)。以下步骤导致我们陷入僵局。请注意,任何时候线程被通知唤醒,它都可以由 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获得锁,将一个字符放入缓冲区,调用notify,退出方法

第7步:
- P2的通知唤醒P3(记住任何线程都可以被唤醒)
- P3 检查等待循环条件,缓冲区中已经有一个字符,因此等待。
- 不再需要调用通知的线程,并且三个线程永久挂起!

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

有用的区别:

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

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

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

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

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

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

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

道格·李 (Doug Lea) 在他的文章中提出了一个有趣的观点 名著: :如果一个 notify()Thread.interrupt() 同时发生,通知实际上可能会丢失。如果这可能发生并产生巨大影响 notifyAll() 是一个更安全的选择,即使您付出了开销的代价(大多数时候唤醒太多线程)。

Java 大师 Joshua Bloch 本人在《Effective Java》第二版中说道:

“第 69 项:更喜欢并发实用程序来等待和通知”。

这是一个例子。运行。然后将其中一个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) { }
        }
    }
}

简短的摘要:

总是更喜欢 通知全部() 超过 通知() 除非您有一个大规模并行应用程序,其中大量线程都执行相同的操作。

解释:

通知() ...]醒来一个线程。因为 通知() 不允许您指定醒来的线程,它仅在大量并行应用程序中有用,即具有大量线程的程序,都做类似的琐事。在这样的应用程序中,您不关心哪个线程被唤醒。

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

比较 通知()通知全部() 在上述情况下:大规模并行应用程序,其中线程执行相同的操作。如果你打电话 通知全部() 在这种情况下, 通知全部() 会引起醒来(即大量线程的调度),其中许多线程是不必要的(因为只有一个线程可以实际继续,即将被授予对象监视器的线程 等待(), 通知(), , 或者 通知全部() 被调用),因此浪费了计算资源。

因此,如果您没有一个应用程序,其中大量线程同时执行相同的操作,则更喜欢 通知全部() 超过 通知(). 。为什么?因为,正如其他用户已经在本论坛中回答的那样, 通知()

唤醒正在该对象的监视器上等待的单个线程。...]选择是 随意的 并由实施酌情发生。

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

想象一下,您有一个生产者消费者应用程序,其中消费者已准备就绪(即 等待() ing)来消费,生产者已经准备好(即 等待() ing)来生产,并且项目队列(要生产/消费)为空。在这种情况下, 通知() 可能只会唤醒消费者,而不会唤醒生产者,因为唤醒谁的选择是 随意的. 。尽管生产者和消费者分别准备好生产和消费,但生产者消费者循环不会取得任何进展。相反,消费者被唤醒(即离开 等待() status),不会因为队列为空而将其从队列中取出,并且 通知() 另一个消费者继续。

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

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

基本上:

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

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

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

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

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

我希望这能消除一些疑虑。

通知() :notify()方法醒来一个线程,等待锁定(该锁上在该锁上称为()的第一个线程)。

通知全部() :notifyAll()方法唤醒所有等待锁的线程;JVM从等待锁定的线程列表中选择一个线程之一,并唤醒线程向上。

单线程的情况下 等待锁时,notify()和notifyAll()没有显着区别。然而,当有多个线程等待锁时,在notify()和notifyAll()中,具体被唤醒的线程是 在JVM的控制下 并且您无法以编程方式控制唤醒特定线程。

乍一看,只调用notify()来唤醒一个线程似乎是个好主意;唤醒所有线程似乎没有必要。然而,问题是 notify() 是唤醒的线程可能不是合适的线程 被唤醒(线程可能正在等待某些其他条件,或者该线程仍然不满足条件等)。 在这种情况下, ,notify() 可能会丢失,并且其他线程都不会唤醒,这可能会导致某种死锁(通知丢失,所有其他线程都永远等待通知)。

为了避免这个问题, ,当有多个线程等待锁(或多个等待完成的条件)时,最好调用notifyAll()。notifyAll()方法会唤醒所有线程,因此效率不是很高。然而,这种性能损失在现实世界的应用中可以忽略不计。

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

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

这是一个更简单的解释:

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

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

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

线程有三种状态。

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

现在,当调用notify()时,JVM会选择一个线程并将它们移至BLOCKED状态,然后移至RUNNING状态,因为没有对监视器对象的竞争。

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

这个答案是对优秀答案的图形重写和简化 沙吉格, ,包括评论者 埃兰.

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

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

制作人:

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

消费者:

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

假设有 2 个生产者和 2 个消费者,共享大小为 1 的缓冲区。下图描绘了一个场景 僵局, ,如果使用所有线程,则可以避免这种情况 通知全部.

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

deadlock due to notify

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();
}

最后,重要的是要了解,如果出现以下情况 notifyAll(),里面的代码 synchronized 已被唤醒的块将按顺序执行,而不是一次全部执行。假设上面的例子中有三个线程在等待,第四个线程调用 notifyAll(). 。所有三个线程都将被唤醒,但只有一个线程开始执行并检查线程的状况 while 环形。如果条件是 true, ,它会调用 wait() 再次,只有那时第二个线程才会开始执行并检查其 while 循环条件等。

取自 博客 关于有效的 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”的评论) 接受的答案 和爪哇 文档):

  • 通知() :JVM 唤醒该对象上等待的线程之一。线程选择是任意的,没有公平性。所以同一个线程可以被一次又一次地唤醒。因此系统的状态发生了变化,但没有取得真正的进展。从而创建了一个 活锁.
  • 通知全部():JVM 唤醒所有线程,然后所有线程争夺该对象上的锁。现在,CPU 调度程序选择一个获取该对象锁的线程。这个选择过程会比 JVM 选择要好得多。从而保证活性。

看看@xagyg 发布的代码。

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

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

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

notify() 唤醒第一个调用的线程 wait() 在同一个物体上。

notifyAll() 唤醒所有调用的线程 wait() 在同一个物体上。

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

我想提一下《Java并发实践》中解释的内容:

第一点,是Notify还是NotifyAll?

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

如果两个线程A和B在相同条件队列的不同条件谓词上等待并称为通知,则最大为JVM,直接JVM将通知。

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

因此,呼叫NotifyAll将解决此问题,但是它将再次具有性能影响,因为它将通知所有线程,并且所有线程都将争夺相同的锁定,并且将涉及上下文开关,从而加载CPU。但是,只有在行为正确的情况下,如果行为本身不正确,我们才应该关心绩效。

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

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

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

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

BLOCKED - 锁定获取被阻止。WAITING - 等待通知(或阻止连接完成)。

总结上面精彩的详细解释,用我能想到的最简单的方式来说,这是由于 JVM 内置监视器的限制,1)是在整个同步单元(块或对象)上获取的,2)不区分正在等待/通知/关于的特定条件。

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

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

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

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

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

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

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

现在我正在研究当我们对一个对象发出notify()时jvm到底是如何识别和中断等待线程的...

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top