Java:notify() 与 notifyAll() 重来一遍

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37026/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 07:28:40  来源:igfitidea点击:

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

javamultithreading

提问by Sergey Mikhanov

If one Googles for "difference between notify()and notifyAll()" then a lot of explanations will pop up (leaving apart the javadoc paragraphs). It all boils down to the number of waiting threads being waken up: one in notify()and all in notifyAll().

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

However (if I do understand the difference between these methods right), only one thread is always selected for further monitor acquisition; in the first case the one selected by the VM, in the second case the one selected by the system thread scheduler. The exact selection procedures for both of them (in the general case) are not known to the programmer.

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

What's the usefuldifference between notify()and notifyAll()then? Am I missing something?

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

采纳答案by Liedman

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

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

That is not correct. o.notifyAll()wakes allof the threads that are blocked in o.wait()calls. The threads are only allowed to return from o.wait()one-by-one, but they each willget their turn.

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



Simply put, it depends on why your threads are waiting to be notified. Do you want to tell one of the waiting threads that something happened, or do you want to tell all of them at the same time?

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

In some cases, all waiting threads can take useful action once the wait finishes. An example would be a set of threads waiting for a certain task to finish; once the task has finished, all waiting threads can continue with their business. In such a case you would use notifyAll()to wake up all waiting threads at the same time.

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

Another case, for example mutually exclusive locking, only one of the waiting threads can do something useful after being notified (in this case acquire the lock). In such a case, you would rather use notify(). Properly implemented, you coulduse notifyAll()in this situation as well, but you would unnecessarily wake threads that can't do anything anyway.

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



In many cases, the code to await a condition will be written as a loop:

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

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

That way, if an o.notifyAll()call wakes more than one waiting thread, and the first one to return from the o.wait()makes leaves the condition in the false state, then the other threads that were awakened will go back to waiting.

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

回答by Spoike

notify()will wake up one thread while notifyAll()will wake up all. As far as I know there is no middle ground. But if you are not sure what notify()will do to your threads, use notifyAll(). Works like a charm everytime.

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

回答by andyuk

I think it depends on how resources are produced and consumed. If 5 work objects are available at once and you have 5 consumer objects, it would make sense to wake up all threads using notifyAll() so each one can process 1 work object.

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

If you have just one work object available, what is the point in waking up all consumer objects to race for that one object? The first one checking for available work will get it and all other threads will check and find they have nothing to do.

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

I found a great explanation here. In short:

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

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.

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

回答by David Crow

Useful differences:

有用的区别:

  • Use notify()if all your waiting threads are interchangeable (the order they wake up doesn't matter), or if you only ever have one waiting thread. A common example is a thread pool used to execute jobs from a queue--when a job is added, one of threads is notified to wake up, execute the next job and go back to sleep.

  • Use notifyAll()for other cases where the waiting threads may have different purposes and should be able to run concurrently. An example is a maintenance operation on a shared resource, where multiple threads are waiting for the operation to complete before accessing the resource.

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

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

回答by David Crow

All the above answers are correct, as far as I can tell, so I'm going to tell you something else. For production code you really should use the classes in java.util.concurrent. There is very little they cannot do for you, in the area of concurrency in java.

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

回答by Steve McLeod

From Joshua Bloch, the Java Guru himself in Effective Java 2nd edition:

来自《Effective Java 2nd edition》中的 Java 大师 Joshua Bloch:

"Item 69: Prefer concurrency utilities to wait and notify".

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

回答by Steve McLeod

Note that with concurrency utilities you also have the choice between signal()and signalAll()as these methods are called there. So the question remains valid even with java.util.concurrent.

需要注意的是与并发性工具,你也有之间的选择signal(),并signalAll()为这些方法被称为那里。所以这个问题即使使用java.util.concurrent.

Doug Lea brings up an interesting point in his famous book: if a notify()and Thread.interrupt()happen at the same time, the notify might actually get lost. If this can happen and has dramatic implications notifyAll()is a safer choice even though you pay the price of overhead (waking too many threads most of the time).

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

回答by Abhay Bansal

Waking up all does not make much significance here. wait notify and notifyall, all these are put after owning the object's monitor. If a thread is in the waiting stage and notify is called, this thread will take up the lock and no other thread at that point can take up that lock. So concurrent access can not take place at all. As far as i know any call to wait notify and notifyall can be made only after taking the lock on the object. Correct me if i am wrong.

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

回答by Erik

Here is an example. Run it. Then change one of the notifyAll() to notify() and see what happens.

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

ProducerConsumerExample class

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 class

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

Consumer class

消费类

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) { }
        }
    }
}

Producer class

生产者类

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) { }
        }
    }
}

回答by xagyg

Clearly, notifywakes (any) one thread in the wait set, notifyAllwakes all threads in the waiting set. The following discussion should clear up any doubts. notifyAllshould be used most of the time. If you are not sure which to use, then use notifyAll.Please see explanation that follows.

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

Read very carefully and understand. Please send me an email if you have any questions.

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

Look at producer/consumer (assumption is a ProducerConsumer class with two methods). IT IS BROKEN (because it uses notify) - yes it MAY work - even most of the time, but it may also cause deadlock - we will see why:

看看生产者/消费者(假设是一个具有两种方法的 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;
}

FIRSTLY,

首先,

Why do we need a while loop surrounding the wait?

为什么我们需要围绕等待的 while 循环?

We need a whileloop in case we get this situation:

while如果我们遇到这种情况,我们需要一个循环:

Consumer 1 (C1) enter the synchronized block and the buffer is empty, so C1 is put in the wait set (via the waitcall). Consumer 2 (C2) is about to enter the synchronized method (at point Y above), but Producer P1 puts an object in the buffer, and subsequently calls notify. The only waiting thread is C1, so it is woken and now attempts to re-acquire the object lock at point X (above).

消费者 1 (C1) 进入同步块并且缓冲区为空,因此 C1 被放入等待集中(通过wait调用)。消费者 2(C2)即将进入同步方法(在上面的 Y 点),但生产者 P1 将一个对象放入缓冲区,随后调用notify. 唯一等待的线程是 C1,因此它被唤醒,现在尝试重新获取点 X(上图)的对象锁。

Now C1 and C2 are attempting to acquire the synchronization lock. One of them (nondeterministically) is chosen and enters the method, the other is blocked (not waiting - but blocked, trying to acquire the lock on the method). Let's say C2 gets the lock first. C1 is still blocking (trying to acquire the lock at X). C2 completes the method and releases the lock. Now, C1 acquires the lock. Guess what, lucky we have a whileloop, because, C1 performs the loop check (guard) and is prevented from removing a non-existent element from the buffer (C2 already got it!). If we didn't have a while, we would get an IndexArrayOutOfBoundsExceptionas C1 tries to remove the first element from the buffer!

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

NOW,

现在,

Ok, now why do we need notifyAll?

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

In the producer/consumer example above it looks like we can get away with notify. It seems this way, because we can prove that the guards on the waitloops for producer and consumer are mutually exclusive. That is, it looks like we cannot have a thread waiting in the putmethod as well as the getmethod, because, for that to be true, then the following would have to be true:

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

buf.size() == 0 AND buf.size() == MAX_SIZE(assume MAX_SIZE is not 0)

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

HOWEVER, this is not good enough, we NEED to use notifyAll. Let's see why ...

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

Assume we have a buffer of size 1 (to make the example easy to follow). The following steps lead us to deadlock. Note that ANYTIME a thread is woken with notify, it can be non-deterministically selected by the JVM - that is any waiting thread can be woken. Also note that when multiple threads are blocking on entry to a method (i.e. trying to acquire a lock), the order of acquisition can be non-deterministic. Remember also that a thread can only be in one of the methods at any one time - the synchronized methods allow only one thread to be executing (i.e. holding the lock of) any (synchronized) methods in the class. If the following sequence of events occurs - deadlock results:

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

STEP 1:
- P1 puts 1 char into the buffer

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

STEP 2:
- P2 attempts put- checks wait loop - already a char - waits

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

STEP 3:
- P3 attempts put- checks wait loop - already a char - waits

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

STEP 4:
- C1 attempts to get 1 char
- C2 attempts to get 1 char - blocks on entry to the getmethod
- C3 attempts to get 1 char - blocks on entry to the getmethod

第 4 步:
- C1 尝试获取 1 个字符
- C2 尝试获取 1 个字符 - 阻止进入get方法
- C3 尝试获取 1 个字符 - 阻止进入get方法

STEP 5:
- C1 is executing the getmethod - gets the char, calls notify, exits method
- The notifywakes up P2
- BUT, C2 enters method before P2 can (P2 must reacquire the lock), so P2 blocks on entry to the putmethod
- C2 checks wait loop, no more chars in buffer, so waits
- C3 enters method after C2, but before P2, checks wait loop, no more chars in buffer, so waits

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

STEP 6:
- NOW: there is P3, C2, and C3 waiting!
- Finally P2 acquires the lock, puts a char in the buffer, calls notify, exits method

第 6 步:
- 现在:有 P3、C2 和 C3 等待!
- 最后 P2 获取锁,在缓冲区中放入一个字符,调用通知,退出方法

STEP 7:
- P2's notification wakes P3 (remember any thread can be woken)
- P3 checks the wait loop condition, there is already a char in the buffer, so waits.
- NO MORE THREADS TO CALL NOTIFY and THREE THREADS PERMANENTLY SUSPENDED!

STEP 7:
- P2 的通知唤醒了 P3(记住任何线程都可以被唤醒)
- P3 检查等待循环条件,缓冲区中已经有一个字符,所以等待。
- 没有更多的线程调用通知和三个线程永久挂起!

SOLUTION: Replace notifywith notifyAllin the producer/consumer code (above).

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