Java:再次通知()与notifyAll()

时间:2008-08-31 18:47:13

标签: java multithreading

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

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

notify()notifyAll()之间的实用区别是什么?我错过了什么吗?

27 个答案:

答案 0 :(得分:314)

显然,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已经得到它!)。如果我们没有while,我们会得到一个IndexArrayOutOfBoundsException,因为C1试图从缓冲区中删除第一个元素!

现在,

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

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

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

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

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

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

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

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

第4步:
- C1尝试获得1个字母 - 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获取锁,将char放入缓冲区,调用notify,退出方法

STEP 7:
- P2的通知唤醒P3(记住任何线程都可以被唤醒)
- P3检查等待循环条件,缓冲区中已有char,所以等待 - 没有更多线索要求通知和三条线路永久性地暂停!

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

答案 1 :(得分:241)

  

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

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


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

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

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


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

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

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

答案 2 :(得分:40)

有用的差异:

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

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

答案 3 :(得分:18)

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

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

我找到了great explanation here。简而言之:

  

通常使用notify()方法   对于资源池,其中   是任意数量的“消费者”   或者“工人”,他们需要资源,但是   将资源添加到池中时   只有一个等待的消费者或   工人可以处理它。该   notifyAll()方法实际上是用在   大多数其他情况。严格来说,它是   要求通知服务员   条件,可以允许多个   服务员继续。但这经常是   很难知道。所以作为一般   规则,如果你没有特别的话   使用notify()的逻辑,然后你   应该使用notifyAll(),   因为通常很难知道   究竟是什么线程将等待   关于特定对象及其原因。

答案 4 :(得分:11)

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

Doug Lea在他的famous book中提出了一个有趣的观点:如果notify()Thread.interrupt()同时发生,通知可能实际上会丢失。如果这种情况发生并且具有重大影响notifyAll()是一个更安全的选择,即使您支付了开销的代价(大多数时间都会唤醒过多的线程)。

答案 5 :(得分:10)

来自Jos Java Bloch,Java Guru本人参加了Effective Java第2版:

“第69项:首选并发实用程序等待并通知”。

答案 6 :(得分:9)

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

答案 7 :(得分:8)

简短摘要:

总是更喜欢 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--

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

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

答案 8 :(得分:5)

我很惊讶没有人提到臭名昭着的“失去的唤醒”问题(google it)。

基本上:

  1. 如果你有多个线程等待相同的条件,
  2. 多个线程,可以让您从状态A转换到状态B,
  3. 多个线程,可以让您从状态B转换到状态A(通常与1.中的线程相同),
  4. 从状态A转换到B应该通知1中的线程。
  5. 然后你应该使用notifyAll,除非你有可证明的保证不可能丢失唤醒。

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

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

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

答案 9 :(得分:5)

我希望这会明白一些疑问。

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

notifyAll() :notifyAll()方法唤醒等待锁定的所有线程; JVM从等待锁定和唤醒的线程列表中选择一个线程 那个帖子。

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

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

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

答案 10 :(得分:4)

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

答案 11 :(得分:4)

这个答案是对xagyg的优秀答案的图形重写和简化,包括eran的评论。

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

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

制片:

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

消费者:

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

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

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

deadlock due to notify

答案 12 :(得分:4)

线程有三种状态。

  1. WAIT - 线程未使用任何CPU周期
  2. BLOCKED - 线程被阻止尝试获取监视器。它可能仍在使用CPU周期
  3. RUNNING - 线程正在运行。
  4. 现在,当调用notify()时,JVM选择一个线程并将它们移动到BLOCKED状态,从而进入RUNNING状态,因为监视器对象没有竞争。

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

答案 13 :(得分:4)

notify()将唤醒一个帖子,而notifyAll()将全部唤醒。据我所知,没有中间立场。但是,如果您不确定notify()将对您的主题做什么,请使用notifyAll()。每次都像魅力一样。

答案 14 :(得分:4)

这是一个更简单的解释:

你是否使用notify()或notifyAll()是正确的,直接结果是其他一个线程正好会获取监视器并开始执行。 (假设某些线程实际上在wait()上阻塞了这个对象,其他无关的线程没有吸收所有可用的核心等等。)影响会在稍后发生。

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

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

答案 15 :(得分:3)

取自有效Java上的blog

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

所以,我理解的是(来自上述博客,#34; Yann TM&#34; accepted answer和Java docs评论):

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

答案 16 :(得分:3)

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()之后唤醒并评估999个线程,然后是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循环条件,依此类推。

答案 17 :(得分:2)

看看@xagyg发布的代码。

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

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

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

答案 18 :(得分:1)

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

答案 19 :(得分:1)

我想提一下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对象可以解决此问题,因为它为每个条件谓词提供了不同的等待。这里它会表现正常并且不会出现性能问题,因为它会调用信号并确保只有一个线程在等待该条件

答案 20 :(得分:1)

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

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

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

答案 21 :(得分:1)

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

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

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

答案 22 :(得分:0)

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

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

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

因此,只有在保证任何等待线程允许进行选择时才能安全地使用notify(),这通常在同一监视器中的所有线程仅检查一个相同条件时得到满足 - 在现实世界的应用程序中相当罕见的情况

答案 23 :(得分:0)

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

这里当您发出相同对象的通知(来自进程/代码的同一/另一侧)时,这将释放一个被阻塞且等待的单个线程(并非所有等待的线程 - 将释放此已发布的线程通过JVM Thread Scheduler和对象上的所有锁获取过程与常规相同。

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

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

现在我正在研究当我们在一个对象上发出notify()时,jvm是如何识别和破坏等待线程的......

答案 24 :(得分:0)

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

答案 25 :(得分:0)

等待队列和阻塞队列

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

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

调用notify时,等待队列中只有一个线程被放入阻塞队列以争夺锁,而notifyAll将等待队列中的所有线程放入阻塞队列。 / p>

现在可以看到区别了吗?
尽管在两种情况下都只会执行一个线程,但是使用notifyAll时,即使其他线程未能争用锁,其他线程仍然可以执行更改(因为它们处于阻塞队列中)。

一些指导原则

基本上,我建议始终使用notifyAll,因为这可能会降低性能。
并且仅在以下情况下使用notify

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

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

  1. 按照@xagyg的建议使用notifyALl
  2. 使生产者和消费者具有不同的锁定对象,并且生产者只能唤醒消费者,消费者只能唤醒生产者。在这种情况下,无论唤醒了哪个使用者,它都可以消耗缓冲区并使程序继续执行。

答案 26 :(得分:-2)

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