Java:notify()与notifyAll()重新开始
如果一个Google的“ notify()
和notifyAll()
之间的区别”,那么会popup很多解释(把javadoc的段落分开)。 这一切归结为被唤醒的等待线程的数量:一个在notify()
,另一个在notifyAll()
。
但是(如果我理解这些方法之间的差异,则只有一个线程总是被select用于进一步的监视器获取; 第一种情况是由VMselect的情况,第二种情况是系统线程调度器select的情况。 他们两个人的确切select程序(在一般情况下)不为程序员所知。
notify()和notifyAll()有什么区别呢? 我错过了什么吗?
简而言之,这取决于为什么你的线程正在等待被通知。 你想告诉一个正在等待的线程发生了什么事,或者你想同时告诉他们所有的人吗?
在一些情况下,一旦等待完成,所有等待的线程可以采取有用的行动。 一个例子是一组等待某个任务完成的线程; 一旦任务完成,所有等待的线程可以继续他们的业务。 在这种情况下,您可以使用notifyAll()同时唤醒所有正在等待的线程。
另一种情况,例如互斥锁,只有一个等待线程可以在收到通知后(在这种情况下获得锁)做一些有用的事情。 在这种情况下,你宁愿使用notify() 。 正确实施,你也可以在这种情况下使用notifyAll() ,但是你会不必要地唤醒那些无法做任何事情的线程。
显然,在等待集中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 (ie 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
循环,以防止出现这种情况:
消费者1(C1)进入同步块并且缓冲区为空,所以C1被置入等待设置(通过wait
呼叫)。 消费者2(C2)即将进入同步方法(在上面的点Y),但生产者P1将一个对象放入缓冲区,随后调用notify
。 唯一的等待线程是C1,所以它被唤醒,现在尝试重新获取点X处的对象锁(上图)。
现在C1和C2正试图获取同步锁。 其中一个(非确定性)被选中并进入该方法,另一个被阻塞(不等待 – 但被阻塞,试图获取该方法的锁)。 假设C2首先获得锁。 C1仍然阻塞(试图获得X的锁)。 C2完成该方法并释放该锁。 现在,C1获得locking。 猜猜是什么,幸运的是我们有一个while
循环,因为C1执行循环检查(guard),并且阻止从缓冲区中删除一个不存在的元素(C2已经知道了!)。 如果我们没有一段while
,我们会得到一个IndexArrayOutOfBoundsException
因为C1试图从缓冲区中删除第一个元素!
现在,
好的,现在为什么我们需要notifyAll?
在上面的生产者/消费者的例子看起来,我们可以逃脱notify
。 这似乎是因为我们可以certificate,生产者和消费者的等待循环上的守卫是相互排斥的。 也就是说,看起来我们不能在put
方法和get
方法中等待一个线程,因为如果这是真的,那么以下就必须是真的:
buf.size() == 0 AND buf.size() == MAX_SIZE
(假设MAX_SIZE不是0)
但是,这不够好,我们需要使用notifyAll
。 我们来看看为什么…
假设我们有一个大小为1的缓冲区(为了使示例容易遵循)。 以下步骤导致我们陷入僵局。 请注意,ANYTIME一个线程被通知唤醒,它可以由JVM非确定性地select – 即任何等待的线程都可以被唤醒。 还要注意的是,当多个线程在进入方法时被阻塞(即尝试获取锁)时,获取的顺序可能是非确定性的。 还要记住,一个线程只能在任何一个方法中 – 同步方法只允许一个线程执行(即保持locking)类中的任何(同步)方法。 如果发生以下一系列事件 – 死锁结果:
步骤1:
– P1将1个字符放入缓冲区
第2步:
– P2尝试put
– 检查等待循环 – 已经是一个字符 – 等待
第3步:
– P3尝试put
– 检查等待循环 – 已经是一个字符 – 等待
步骤4:
– C1试图获得1个字符
– C2尝试获取1个字符块 – 进入get
方法
– C3尝试获取1个字符块 – 进入get
方法
第5步:
– C1正在执行get
方法 – 获取char,调用notify
,exits方法
– notify
唤醒P2
– 但是,C2在P2可以前进入方法(P2必须重新获得锁),所以P2阻塞进入put
方法
– C2检查等待循环,缓冲区中不再有字符,等待
– C3进入C2之后,但在P2之前,检查等待循环,缓冲区中不再有字符,所以等待
步骤6:
– 现在:P3,C2和C3正在等待!
– 最后P2获取锁,在缓冲区中放置一个char,调用notify,退出方法
STEP 7:
– P2的通知唤醒P3(记住任何线程都可以唤醒)
– P3检查等待循环条件,缓冲区中已经有一个char,所以等待。
– 没有更多的线索呼吁通知和三线永久暂停!
解决scheme:用生产者/消费者代码(上面)中的notifyAll
replacenotify
。
有用的区别:
-
如果所有等待的线程都可以互换,则使用notify() (它们的唤醒顺序无关紧要),或者只有一个等待线程。 一个常见的例子是线程池,用于执行队列中的作业 – 添加作业时,会通知其中一个线程唤醒,执行下一个作业并返回到hibernate状态。
-
对于等待线程可能具有不同用途且应该能够并发运行的其他情况,请使用notifyAll() 。 一个示例是共享资源上的维护操作,其中多个线程正在等待操作完成,然后才能访问资源。
我认为这取决于如何生产和消费资源。 如果一次有5个工作对象可用,并且有5个消费者对象,则使用notifyAll()唤醒所有线程是有意义的,因此每个线程都可以处理1个工作对象。
如果只有一个工作对象可用,那么唤醒所有消费者对象以争夺该对象有什么意义? 第一个检查可用的工作将得到它,所有其他线程将检查,发现他们无事可做。
我在这里find一个很好的解释 。 简而言之:
notify()方法通常用于资源池 ,其中有任意数量的占用资源的“消费者”或“工作者”,但是当资源添加到池中时,只有一个等待的消费者或工人可以处理用它。 notifyAll()方法实际上在大多数情况下使用。 严格地说,要求服务员通知可能允许多个服务员进行的条件。 但是这通常很难知道。 所以一般来说, 如果你没有特别的使用notify()的逻辑,那么你应该使用notifyAll() ,因为通常很难确切地知道哪些线程将在特定的对象上等待以及为什么。
来自Joshua Bloch,Java Guru本人在Effective Java第2版中的演讲:
“项目69:喜欢并发实用程序等待和通知”。
请注意,对于并发实用程序,您也可以在signal()
和signalAll()
之间进行select,因为这些方法在那里被调用。 所以即使使用java.util.concurrent
,问题仍然有效。
Doug Lea在他着名的书中提出了一个有趣的观点:如果notify()
和Thread.interrupt()
同时发生,notify可能实际上会丢失。 如果这可能发生并且具有戏剧性的含义notifyAll()
是一个更安全的select,即使你花费开销的代价(在大多数时间唤醒太多的线程)。
这是一个例子。 运行。 然后改变一个notifyAll()来通知()并看看会发生什么。
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) { } } } }
简短的摘要:
始终优先notifyAll()通过notify(),除非你有一个大量的并行应用程序,大量的线程都做同样的事情。
说明:
notify()唤醒单线程。 由于notify()不允许指定被唤醒的线程,因此它仅适用于大规模并行应用程序 – 也就是具有大量线程的程序,所有这些都执行类似的工作。 在这样的应用程序中,你不关心哪个线程被唤醒。
来源: https : //docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
在上面描述的情况下比较notify()和notifyAll() :一个大规模并行的应用程序,线程正在做同样的事情。 如果在这种情况下调用notifyAll() ,则notifyAll()将引起大量线程的唤醒(即调度),其中许multithreading是不必要的(因为只有一个线程可以实际进行,即将被授予的线程监视对象wait() , notify()或notifyAll()被调用),因此浪费计算资源。
因此,如果没有大量线程同时执行相同操作的应用程序,则优先notifyAll()通过notify() 。 为什么? 因为,正如其他用户已经在这个论坛上回答, 通知()
唤醒在该对象的监视器上等待的单个线程。 […]的select是任意的,并根据实施的决定发生。
来源:Java SE8 API( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify– )
假设你有一个生产者消费者应用程序,消费者已经准备好(即等待)消费,生产者已经准备好(即等待 )生产,并且(生产/消费)项目的队列是空的。 在这种情况下, notify()可能只会唤醒消费者而不会唤醒生产者,因为唤醒的select是任意的 。 虽然生产者和消费者分别准备好生产和消费,但生产者消费者周期不会有任何进展。 相反,消费者被唤醒(即,离开wait()状态),不会从队列中取出项目,因为它是空的,并且通知另一个消费者继续。
相反, notifyAll()会唤醒生产者和消费者。 调度者的select取决于调度者。 当然,根据调度程序的实现情况,调度程序也可能只调度消费者(例如,如果将消费者线程分配给一个非常高的优先级)。 然而,这里的假设是调度器只调度消费者的危险性低于JVM只唤醒消费者的危险,因为任何合理实现的调度器都不会做出任意的决定。 相反,大多数调度程序实现至less需要一些努力来防止饥饿。
我很惊讶,没有人提到臭名昭着的“失落的唤醒”问题(谷歌它)。
基本上:
- 如果你有多个线程等待相同的条件,
- 多个线程可以让你从状态A转换到状态B,
- 多个线程可以使您从状态B转换到状态A(通常与1中的线程相同)
- 从状态A转换到B应该通知1中的线程。
那么你应该使用notifyAll,除非你有可certificate的保证,失去唤醒是不可能的。
一个常见的例子是一个并发FIFO队列,其中:多个队列队列(上面的1.和3.)可以将你的队列从空的队列转换到非空的多个队列队列(2.上面),可以等待条件“队列不空” – >非空应通知出队员
你可以很容易地写出一个交错的操作,其中从一个空队列开始,2个入队队列和2个出列队列交互,1个队列将保持hibernate状态。
这是一个可以与死锁问题相媲美的问题。
我希望这会澄清一些疑虑。
notify() :notify()方法唤醒一个等待锁的线程(第一个在该锁上调用wait()的线程)。
notifyAll() :notifyAll()方法唤醒所有等待锁的线程; JVM从等待locking的线程列表中select一个线程并唤醒线程。
在单个线程等待锁的情况下, notify()和notifyAll()之间没有显着差异。 但是,当有多个线程等待locking时,在notify()和notifyAll()中,被唤醒的确切线程都在JVM的控制之下,并且不能以编程方式控制唤醒特定线程。
乍一看,似乎只是调用notify()来唤醒一个线程是一个好主意; 看起来没有必要唤醒所有的线程。 但是, notify()的问题是被唤醒的线程可能不是合适的被唤醒线程(线程可能正在等待其他条件,或者该线程的条件仍然不满足)。 在这种情况下 ,notify()可能会丢失,没有其他线程会被唤醒,从而导致一种死锁(通知丢失,所有其他线程都在等待通知)。
为了避免这个问题 ,当有多个线程在等待一个锁(或多个等待的条件)时调用notifyAll()会更好。 notifyAll()方法唤醒所有线程,所以效率不高。 然而,这种性能损失在现实世界的应用中可以忽略不计。
以上所有答案都是正确的,据我所知,所以我会告诉你一些其他的东西。 对于生产代码,你真的应该使用java.util.concurrent中的类。 在java中的并发领域,他们为你做的事情很less。
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()
方法,只有一个线程会被唤醒。
当需要select哪个线程将接下来的工作时使用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
循环条件,依此类推。
notify()
唤醒在同一个对象上调用wait()
的第一个线程。
notifyAll()
唤醒所有在同一个对象上调用wait()
的线程。
最高优先级的线程将首先运行。
看一下@xagyg发布的代码。
假设两个不同的线程正在等待两个不同的条件:
第一个线程正在等待buf.size() != MAX_SIZE
, 第二个线程正在等待buf.size() != 0
。
假设在某个时候buf.size()
不等于0 。 JVM调用notify()
而不是notifyAll()
,并且通知第一个线程(而不是第二个线程)。
第一个线程被唤醒,检查可能返回MAX_SIZE
buf.size()
,然后返回等待状态。 第二个线程不会被唤醒,继续等待,不会调用get()
。
notify()
会唤醒一个线程,而notifyAll()
会唤醒所有线程。 据我所知,没有中间立场。 但是,如果您不确定notify()
将对您的线程执行什么操作,请使用notifyAll()
。 每次都像魅力一样。
这是一个更简单的解释:
无论你使用notify()还是notifyAll(),你都是正确的,直接的结果就是一个其他的线程将获取监视器并开始执行。 (假设一些线程实际上被阻塞在wait()这个对象上,其他不相关的线程没有吸收所有可用的内核,等等)。
假设线程A,B和C正在等待这个对象,并且线程A获得了这个监视器。 区别在于A发布监视器后会发生什么情况。 如果使用notify(),那么B和C在wait()中仍然被阻塞:它们不在监视器上等待,它们正在等待被通知。 当A释放监视器时,B和C仍然坐在那里,等待通知()。
如果使用notifyAll(),那么B和C都已经超越“等待通知”状态,并且都等待获取监视器。 当A释放监视器时,B或C将获取它(假设没有其他线程正在竞争该监视器)并开始执行。
我想提一下Java并发实践中的解释:
首先,是否通知或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中使用显式lockingLock的Condition对象来解决,因为它为每个条件谓词提供了不同的等待。 在这里它将performance正确,不会有性能问题,因为它会调用信号,并确保只有一个线程正在等待这种情况
notify将只通知处于等待状态的一个线程,而通知all将通知处于等待状态的所有线程,现在所有通知的线程和所有被阻塞的线程都有资格获得锁,其中只有一个会获得锁,所有其他人(包括处于等待状态的人)将处于阻塞状态。
notify()
– 从对象的等待集合中select一个随机线程,并将其置于BLOCKED
状态。 对象的等待集中的其余线程仍处于WAITING
状态。
notifyAll()
– 将对象的等待集中的所有线程移至BLOCKED
状态。 在使用notifyAll()
,在共享对象的等待集中没有剩余线程,因为它们都处于BLOCKED
状态,而不处于WAITING
状态。
已locking – locking获取locking。 WAITING
– 等待通知(或阻止join完成)。
一个线程有三个状态。
- 等待 – 线程不使用任何CPU周期
- BLOCKED – 线程被阻塞,试图获取monitor.It可能仍然使用CPU周期
- 正在运行 – 线程正在运行。
现在,当notify()被调用时,JVMselect一个线程并将它们移动到BLOCKED状态,从而进入RUNNING状态,因为没有监视器对象的竞争。
当notifyAll()被调用时,JVM选取所有的线程,并把它们全部移到BLOCKED状态。 所有这些线程将优先获得对象的locking。首先能够获取监视器的线程将能够首先进入RUNNING状态,依此类推。
所有的答案,说notifyAll()唤醒所有的线程,然后随机select一个线程是错误的 。
notifyAll()
selects a thread with the highest priority and then let's them work according to their priority.
This answer is a graphical rewriting and simplification of the excellent answer by xagyg , including comments by eran .
Why use notifyAll, even when each product is intended for a single consumer?
Consider producers and consumers, simplified as follows.
Producer:
while (!empty) { wait() // on full } put() notify()
Consumer:
while (empty) { wait() // on empty } take() notify()
Assume 2 producers and 2 consumers, sharing a buffer of size 1. The following picture depicts a scenario leading to a deadlock , which would be avoided if all threads used notifyAll .
Each notify is labeled with the thread being woken up.
To summarize the excellent detailed explanations above, and in the simplest way I can think of, this is due to the limitations of the JVM built-in monitor, which 1) is acquired on the entire synchronization unit (block or object) and 2) does not discriminate about the specific condition being waited/notified on/about.
This means that if multiple threads are waiting on different conditions and notify() is used, the selected thread may not be the one which would make progress on the newly fulfilled condition – causing that thread (and other currently still waiting threads which would be able to fulfill the condition, etc..) not to be able to make progress, and eventually starvation or program hangup.
In contrast, notifyAll() enables all waiting threads to eventually re-acquire the lock and check for their respective condition, thereby eventually allowing progress to be made.
So notify() can be used safely only if any waiting thread is guaranteed to allow progress to be made should it be selected, which in general is satisfied when all threads within the same monitor check for only one and the same condition – a fairly rare case in real world applications.
When you call the wait() of the "object"(expecting the object lock is acquired),intern this will release the lock on that object and help's the other threads to have lock on this "object", in this scenario there will be more than 1 thread waiting for the "resource/object"(considering the other threads also issued the wait on the same above object and down the way there will be a thread that fill the resource/object and invokes notify/notifyAll).
Here when you issue the notify of the same object(from the same/other side of the process/code),this will release a blocked and waiting single thread (not all the waiting threads — this released thread will be picked by JVM Thread Scheduler and all the lock obtaining process on the object is same as regular).
If you have Only one thread that will be sharing/working on this object , it is ok to use the notify() method alone in your wait-notify implementation.
if you are in situation where more than one thread read's and writes on resources/object based on your business logic,then you should go for notifyAll()
now i am looking how exactly the jvm is identifying and breaking the waiting thread when we issue notify() on a object …
While there are some solid answers above, I am surprised by the number of confusions and misunderstandings I have read. This probably proves the idea that one should use java.util.concurrent as much as possible instead of trying to write own broken concurrent code. Back to the question: to summarize, the best practice today is to AVOID notify() in ALL situations due to the lost wakeup problem. Anyone who doesn't understand this should not be allowed to write mission critical concurrency code. If you are worried about the herding problem, one safe way to achieve waking one thread up at a time is to: 1. Build an explicit waiting queue for the waiting threads; 2. Have each of the thread in the queue wait for it's predecessor; 3. Have each thread call notifyAll() when done. Or you can use Java.util.concurrent.*, which have already implemented this.
Taken from blog on 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.
So, what i understand is (from aforementioned blog, comment by "Yann TM" on accepted answer and Java docs ):
- notify() : JVM awakens one of the waiting threads on this object. Thread selection is made arbitrarily without fairness. So same thread can be awakened again and again. So system's state changes but no real progress is made. Thus creating a livelock .
- notifyAll() : JVM awakens all threads and then all threads race for the lock on this object. Now, CPU scheduler selects a thread which acquires lock on this object. This selection process would be much better than selection by JVM. Thus, ensuring liveness.
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.