Java 为什么总是在循环内调用 wait()
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1038007/
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
Why should wait() always be called inside a loop
提问by Geek
I have read that we should always call a wait()
from within a loop:
我读过我们应该始终wait()
从循环中调用 a :
while (!condition) { obj.wait(); }
It works fine without a loop so why is that?
它在没有循环的情况下工作正常,为什么会这样?
回答by Philipp
There might be more then just one worker waiting for a condition to become true.
可能有不止一个工人在等待条件变为真。
If two or more worker get awake (notifyAll) they have to check the condition again. otherwise all workers would continue even though there might only be data for one of them.
如果两个或更多工人醒来(notifyAll),他们必须再次检查情况。否则所有工人都会继续工作,即使可能只有其中一位工人的数据。
回答by Aaron Maenpaa
Because wait and notify are used to implement [condition variables](http://en.wikipedia.org/wiki/Monitor_(synchronization)#Blocking_condition_variables)and so you need to check whether the specific predicate you're waiting on is true before continuing.
因为wait和notify是用来实现[条件变量]( http://en.wikipedia.org/wiki/Monitor_( synchronization)#Blocking_condition_variables)所以你需要先检查你等待的特定谓词是否为真继续。
回答by akarnokd
You need not only to loop it but check your condition in the loop. Java does not guarantee that your thread will be woken up only by a notify()/notifyAll() call or the right notify()/notifyAll() call at all. Because of this property the loop-less version might work on your development environment and fail on the production environment unexpectedly.
您不仅需要循环它,还需要检查循环中的条件。Java 不保证您的线程只会被notify()/notifyAll() 调用或正确的notify()/notifyAll() 调用唤醒。由于此属性,无循环版本可能会在您的开发环境中运行,而在生产环境中会意外失败。
For example, you are waiting for something:
例如,您正在等待某事:
synchronized (theObjectYouAreWaitingOn) {
while (!carryOn) {
theObjectYouAreWaitingOn.wait();
}
}
An evil thread comes along and:
一个邪恶的线程出现了:
theObjectYouAreWaitingOn.notifyAll();
If the evil thread does not/can not mess with the carryOn
you just continue to wait for the proper client.
如果邪恶的线程没有/不能惹恼carryOn
你,你就继续等待合适的客户端。
Edit:Added some more samples. The wait can be interrupted. It throws InterruptedException and you might need to wrap the wait in a try-catch. Depending on your business needs, you can exit or suppress the exception and continue waiting.
编辑:添加了更多示例。可以中断等待。它抛出 InterruptedException 并且您可能需要将等待包装在 try-catch 中。根据您的业务需求,您可以退出或抑制异常并继续等待。
回答by Tadeusz Kopec
It's answered in documentation for Object.wait(long milis)
它在Object.wait(long milis) 的文档中得到了回答
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防止它,如果条件不满足则继续等待。换句话说,等待应该总是在循环中发生,就像这样:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
(For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
(有关此主题的更多信息,请参阅 Doug Lea 的“Java 中的并发编程(第二版)”(Addison-Wesley,2000)中的第 3.2.3 节,或 Joshua Bloch 的“Effective Java Programming Language Guide”(Addison-韦斯利,2001 年)。
回答by Gray
Why should wait() always be called inside a loop
为什么总是在循环内调用 wait()
The primary reason why while
loops are so important is race conditions between threads. Certainly spurious wakeups are real and for certain architectures they are common, but race conditions are a much more likely reason for the while
loop.
while
循环如此重要的主要原因是线程之间的竞争条件。当然虚假唤醒是真实的,并且对于某些架构它们很常见,但竞争条件是while
循环的一个更可能的原因。
For example:
例如:
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
With the above code, there may be 2 consumer threads. When the producer locks the queue
to add to it, consumer #1 may be blocked at the synchronized
lock while consumer #2 is waiting on the queue
. When the item is added to the queue and notify
called by the producer, #2 is moved from the wait queue to be blocked on the queue
lock, but it will be behindthe #1 consumer which was already blocked on the lock. This means that the #1 consumer gets to go forward first to call remove()
from the queue
. If the while
loop is just an if
, then when consumer #2 gets the lock after #1 and calls remove()
, an exception would occur because the queue
is now empty -- the other consumer thread already removed the item. Even though it was notified, it needs to be make sure the queue
is for sure not empty because of this race condition.
用上面的代码,可能有2个消费者线程。当生产者锁定queue
要添加到它synchronized
的queue
. 当项目被添加到队列并被notify
生产者调用时,#2 会从等待队列中移出以在queue
锁上被阻塞,但它将落后于已经被锁阻塞的 #1 消费者。这意味着 #1 消费者可以remove()
先从queue
. 如果while
循环只是一个if
,那么当消费者#2 在#1 之后获得锁并调用 时remove()
,会发生异常,因为queue
现在是空的——另一个消费者线程已经删除了该项目。即使它被通知了,它也需要queue
确保由于这种竞争条件而不是空的。
This well documented. Here's a web page I created a while back which explains the race condition in detailand has some sample code.
这有据可查。这是我不久前创建的一个网页,它详细解释了竞争条件并有一些示例代码。
回答by Adil
From your Question:
从你的问题:
I have read that we should always called a wait() from within a loop:
我读过我们应该始终从循环中调用 wait() :
Although wait( ) normally waits until notify( ) or notifyAll( ) is called, there is a possibility that in very rare cases the waiting thread could be awakened due to a spurious wakeup. In this case, a waiting thread resumes without notify( ) or notifyAll( ) having been called.
尽管wait() 通常会等到notify() 或notifyAll() 被调用,但在极少数情况下,等待线程可能会由于虚假唤醒而被唤醒。在这种情况下,等待线程在没有调用 notify() 或 notifyAll() 的情况下恢复。
In essence, the thread resumes for no apparent reason.
从本质上讲,线程会无缘无故地恢复。
Because of this remote possibility, Oracle recommends that calls to wait( ) should take place within a loop that checks the condition on which the thread is waiting.
由于这种可能性很小,Oracle 建议对 wait() 的调用应在检查线程等待条件的循环中进行。
回答by Sameer Patel
Both safety and liveness are concerns when using the wait/notify mechanism. The safety property requires that all objects maintain consistent states in a multithreaded environment. The liveness property requires that every operation or method invocation execute to completion without interruption.
使用等待/通知机制时,安全性和活性都是需要考虑的问题。安全属性要求所有对象在多线程环境中保持一致的状态。liveness 属性要求每个操作或方法调用都执行到完成而不会中断。
To guarantee liveness, programs must test the while loop condition before invoking the wait() method. This early test checks whether another thread has already satisfied the condition predicate and sent a notification. Invoking the wait() method after the notification has been sent results in indefinite blocking.
为了保证活性,程序必须在调用 wait() 方法之前测试 while 循环条件。这个早期测试检查另一个线程是否已经满足条件谓词并发送通知。在发送通知后调用 wait() 方法会导致无限期阻塞。
To guarantee safety, programs must test the while loop condition after returning from the wait() method. Although wait() is intended to block indefinitely until a notification is received, it still must be encased within a loop to prevent the following vulnerabilities:
为了保证安全,程序必须在从 wait() 方法返回后测试 while 循环条件。尽管 wait() 旨在无限期地阻塞直到收到通知,但它仍然必须包含在循环中以防止以下漏洞:
Thread in the middle:A third thread can acquire the lock on the shared object during the interval between a notification being sent and the receiving thread resuming execution. This third thread can change the state of the object, leaving it inconsistent. This is a time-of-check, time-of-use (TOCTOU) race condition.
中间线程:第三个线程可以在发送通知和接收线程恢复执行之间的时间间隔内获取共享对象上的锁。这第三个线程可以更改对象的状态,使其不一致。这是一个检查时间、使用时间 (TOCTOU) 竞争条件。
Malicious notification:A random or malicious notification can be received when the condition predicate is false. Such a notification would cancel the wait() method.
恶意通知:当条件谓词为假时,可以收到随机或恶意通知。这样的通知将取消 wait() 方法。
Misdelivered notification:The order in which threads execute after receipt of a notifyAll() signal is unspecified. Consequently, an unrelated thread could start executing and discover that its condition predicate is satisfied. Consequently, it could resume execution despite being required to remain dormant.
错误传递通知:未指定线程在收到 notifyAll() 信号后执行的顺序。因此,一个不相关的线程可以开始执行并发现它的条件谓词得到满足。因此,尽管需要保持休眠状态,但它仍可以恢复执行。
Spurious wakeups:Certain Java Virtual Machine (JVM) implementations are vulnerable to spurious wakeups that result in waiting threads waking up even without a notification.
虚假唤醒:某些 Java 虚拟机 (JVM) 实现容易受到虚假唤醒的影响,导致等待线程在没有通知的情况下唤醒。
For these reasons, programs must check the condition predicate after the wait() method returns. A while loop is the best choice for checking the condition predicate both before and after invoking wait().
由于这些原因,程序必须在 wait() 方法返回后检查条件谓词。while 循环是在调用 wait() 之前和之后检查条件谓词的最佳选择。
Similarly, the await() method of the Condition interface also must be invoked inside a loop. According to the Java API, Interface Condition
同样,Condition 接口的 await() 方法也必须在循环内调用。根据 Java API,接口条件
When waiting upon a Condition, a "spurious wakeup" is permitted to occur, in general, as a concession to the underlying platform semantics. This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for. An implementation is free to remove the possibility of spurious wakeups but it is recommended that applications programmers always assume that they can occur and so always wait in a loop.
在等待条件时,通常允许发生“虚假唤醒”,作为对底层平台语义的让步。这对大多数应用程序几乎没有实际影响,因为应始终在循环中等待条件,以测试正在等待的状态谓词。实现可以自由地消除虚假唤醒的可能性,但建议应用程序程序员始终假设它们可能发生,因此始终在循环中等待。
New code should use the java.util.concurrent.locks concurrency utilities in place of the wait/notify mechanism. However, legacy code that complies with the other requirements of this rule is permitted to depend on the wait/notify mechanism.
新代码应使用 java.util.concurrent.locks 并发实用程序代替等待/通知机制。但是,允许符合此规则其他要求的遗留代码依赖于等待/通知机制。
Noncompliant Code ExampleThis noncompliant code example invokes the wait() method inside a traditional if block and fails to check the postcondition after the notification is received. If the notification were accidental or malicious, the thread could wake up prematurely.
不符合要求的代码示例这不符合要求的代码示例调用内部的传统如果块的等待()方法和失败时,接收到该通知后,检查后置条件。如果通知是意外的或恶意的,线程可能会过早唤醒。
synchronized (object) {
if (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
Compliant SolutionThis compliant solution calls the wait() method from within a while loop to check the condition both before and after the call to wait():
合规解决方案此合规解决方案从 while 循环内调用 wait() 方法以检查调用 wait() 之前和之后的条件:
synchronized (object) {
while (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
Invocations of the java.util.concurrent.locks.Condition.await() method also must be enclosed in a similar loop.
java.util.concurrent.locks.Condition.await() 方法的调用也必须包含在类似的循环中。
回答by user104309
I think I got @Gray 's answer.
我想我得到了@Gray 的答案。
Let me try to rephrase that for newbies like me and request the experts to correct me if I am wrong.
让我试着为像我这样的新手改写一下,如果我错了,请专家纠正我。
Consumer synchronized block::
消费者同步块:
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
Producer synchronized block::
生产者同步块::
synchronized(queue) {
// producer produces inside the queue
queue.notify();
}
Assume the following happens in the given order:
假设以下按给定的顺序发生:
1) consumer#2 gets inside the consumer synchronized
block and is waiting since queue is empty.
1) 消费者#2 进入消费者synchronized
块并等待,因为队列为空。
2) Now, producer obtains the lock on queue
and inserts inside the queue and calls notify().
2) 现在,生产者获得锁queue
并插入队列并调用notify()。
Now,either consumer#1 can be chosen to run which is waiting for queue
lock to enter the synchronized
block for the first time
现在,可以选择第一次等待queue
锁进入synchronized
块的消费者#1运行
or
或者
consumer#2 can be chosen to run.
可以选择消费者#2 运行。
3) say, consumer#1 is chosen to continue with the execution. When it checks the condition,it will be true and it will remove()
from the queue.
3) 说,消费者#1 被选择继续执行。当它检查条件时,它将为真并且remove()
将从队列中删除。
4) say,consumer#2 is proceeding from where it halted its execution (the line after the wait()
method). If 'while' condition is not there (instead an if
condition), it will just proceed to call remove()
which might result in an exception/unexpected behaviour.
4) 说,consumer#2 从它停止执行的地方开始(wait()
方法之后的那一行)。如果“while”条件不存在(而不是if
条件),它将继续调用remove()
,这可能会导致异常/意外行为。
回答by Nathan Hughes
Three things you will see people do:
你会看到人们做的三件事:
Using wait without checking anything (BROKEN)
Using wait with a condition, using an if check first (BROKEN).
Using wait in a loop, where the loop test checks the condition (NOT BROKEN).
使用等待而不检查任何内容(BROKEN)
使用带条件的等待,首先使用 if 检查 (BROKEN)。
在循环中使用等待,其中循环测试检查条件(未中断)。
Not appreciating these details about how wait and notify work leads people to choose the wrong approach:
不了解等待和通知工作如何导致人们选择错误方法的这些细节:
One is that a thread doesn't remember notifications that happened before it got around to waiting. The notify and notifyAll methods only effect threads that are already waiting, if a thread isn't waiting at the time it is out of luck.
Another is that a thread releases the lock once it starts waiting. Once it gets a notification it re-acquires the lock and continues on where it left off. Releasing the lock means that thread does not know anythingabout the current state once it wakes back up, any number of other threads could have made changes since then. The check made before the thread started waiting doesn't tell you anything about what the state is currently.
一个是线程不记得在等待之前发生的通知。notify 和 notifyAll 方法只影响已经在等待的线程,如果一个线程没有在等待,那么它就不走运了。
另一个是线程一旦开始等待就会释放锁。一旦收到通知,它会重新获取锁定并从停止的地方继续。解除锁定意味着线程不知道任何有关目前的状态,一旦被唤醒,任何数量的其他线程可能从那时起所做的更改。在线程开始等待之前所做的检查不会告诉您有关当前状态的任何信息。
So the first case, with no checking, lays your code vulnerable to race conditions. It might happen to work by accident if one thread has enough of a head start over another. Or you may have threads waiting forever. If you sprinkle in timeouts then you end up with slow code that sometimes doesn't do what you want.
因此,没有检查的第一种情况会使您的代码容易受到竞争条件的影响。如果一个线程比另一个线程有足够的领先优势,它可能会偶然工作。或者你可能让线程永远等待。如果你加入超时,那么你最终会得到缓慢的代码,有时不会做你想要的。
Adding a condition to check apart from the notification itself protects your code from these race conditions and gives your code a way to know what the state is even if the thread wasn't waiting at the right time.
在通知本身之外添加一个条件来检查可以保护您的代码免受这些竞争条件的影响,并为您的代码提供一种方法来了解状态是什么,即使线程没有在正确的时间等待。
The second case, with if-checks, is likely to work if you have only 2 threads. That puts a limit on the number of states things can get into and when you made faulty assumptions you don't get burned so badly. This is the situation for lots of toy example code exercises. The result is people come away thinking they understand, when they really don't.
如果您只有 2 个线程,则使用 if 检查的第二种情况可能会起作用。这限制了事物可以进入的状态数量,并且当您做出错误的假设时,您不会受到如此严重的影响。这是许多玩具示例代码练习的情况。结果是人们离开时以为他们理解了,但实际上他们并不理解。
Protip: Real world code has more than two threads.
提示:现实世界的代码有两个以上的线程。
Using the loop lets you re-check the condition once you re-acquire the lock so that you're moving forward based on current state, not on stale state.
使用循环可以让您在重新获取锁定后重新检查条件,以便您根据当前状态而不是陈旧状态继续前进。
回答by aravind s
In simple words,
简单来说,
'if' is a conditional statement , once condition is satisfied remaining block of code will get executed.
'if' 是一个条件语句,一旦满足条件,剩余的代码块将被执行。
'while' is a loop which going check the condition unless condition is not satisfied.
'while' 是一个循环,它会检查条件,除非条件不满足。