Java 为什么总是在循环中调用wait()
我已经读到,我们应该始终从循环中调用Java 为什么总是在循环中调用wait(),java,multithreading,Java,Multithreading,我已经读到,我们应该始终从循环中调用wait(): while (!condition) { obj.wait(); } 它在没有循环的情况下可以正常工作,这是为什么?可能有不止一个工人在等待条件变为真 如果两个或两个以上的工人醒来(notifyAll),他们必须再次检查情况。 否则,即使可能只有其中一个工人的数据,所有工人都将继续工作。因为wait和notify用于实现[条件变量](因此,在继续之前,您需要检查正在等待的特定谓词是否为true。您不仅需要循环它,还需要检查循环中的条件。Jav
wait()
:
while (!condition) { obj.wait(); }
它在没有循环的情况下可以正常工作,这是为什么?可能有不止一个工人在等待条件变为真 如果两个或两个以上的工人醒来(notifyAll),他们必须再次检查情况。
否则,即使可能只有其中一个工人的数据,所有工人都将继续工作。因为wait和notify用于实现[条件变量](因此,在继续之前,您需要检查正在等待的特定谓词是否为true。您不仅需要循环它,还需要检查循环中的条件。Java不保证仅通过notify()/notifyAll()调用或正确的notify()/notifyAll()来唤醒线程由于此属性,无循环版本可能会在您的开发环境中工作,并在生产环境中意外失败 例如,您正在等待某事:
synchronized (theObjectYouAreWaitingOn) {
while (!carryOn) {
theObjectYouAreWaitingOn.wait();
}
}
一条邪恶的线索出现了:
theObjectYouAreWaitingOn.notifyAll();
如果邪恶线程没有/不能干扰carryOn
,您只需继续等待合适的客户端
编辑:添加了更多示例。
等待可能会被中断。它会引发InterruptedException,您可能需要将等待包装在一个try-catch中。根据您的业务需要,您可以退出或抑制异常并继续等待。中对此进行了回答
线程也可以在不被通知、中断或超时的情况下被唤醒,即所谓的虚假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,并在不满足条件的情况下继续等待来防止这种情况。换句话说,等待ould总是以循环的形式出现,如下所示:
synchronized(obj){
而()
对象等待(超时);
…//执行适合条件的操作
}
(有关此主题的更多信息,
见Doug Lea的第3.2.3节
Java中的并发编程
(第二版)”,
2000),或Joshua Bloch的第50项
有效的Java编程语言
指南”(艾迪生·韦斯利,2001年)
为什么总是在循环中调用wait()
while
循环之所以如此重要,主要原因是线程之间的竞争条件。当然,虚假唤醒是真实的,对于某些体系结构来说,它们很常见,但竞争条件更可能是while
循环的原因
例如:
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
使用上述代码,可能有2个使用者线程。当生产者锁定队列
以添加到其中时,使用者1可能在同步
锁定处被阻止,而使用者2正在等待队列
。当项目添加到队列并由生产者调用通知
时,使用者2将从wai移动t要在队列
锁上阻塞的队列,但它将位于已在锁上阻塞的#1使用者后面。这意味着#1使用者首先要向前调用remove()
来自队列
。如果while
循环只是一个If
,那么当消费者2在#1之后获得锁并调用remove()
,将发生异常,因为队列
现在为空--另一个使用者线程已删除该项目。即使它已收到通知,它也需要确保队列不会因为此竞争条件而为空
这是一个很好的文档。这是我不久前创建的一个网页,它解释了,并有一些示例代码。根据您的问题:
我已经读到,我们应该始终从循环中调用wait():
while (!condition) { obj.wait(); }
虽然wait()通常会等待直到调用notify()或notifyAll(),但在极少数情况下,等待的线程可能会由于虚假唤醒而被唤醒。在这种情况下,等待的线程会在未调用notify()或notifyAll()的情况下恢复
本质上,线程恢复没有明显的原因
由于这种可能性很小,Oracle建议调用wait()应该在检查线程正在等待的条件的循环中发生。使用等待/通知机制时,安全性和活动性都是需要考虑的问题。安全性属性要求所有对象在多线程环境中保持一致的状态。活动性属性要求每个操作或方法都必须从执行到完成,不间断 为了保证活动性,程序必须在调用wait()方法之前测试while循环条件。此早期测试检查另一个线程是否已满足条件谓词并发送了通知。在发送通知后调用wait()方法会导致无限阻塞 为了保证安全,程序必须在从wait()方法返回后测试while循环条件。虽然wait()旨在无限期阻止,直到收到通知为止,但它仍然必须封装在循环中,以防止出现以下漏洞: 中间的线程:第三个线程可以在发送通知和接收线程恢复执行之间的时间间隔内获取共享对象的锁。该第三个线程可以更改对象的状态,使其不一致。这是检查时间、使用时间(TOCTOU)竞争条件 恶意通知:当条件谓词为false时,可能会收到随机或恶意通知。此类通知将取消wait()方法 误发通知:未指定线程在收到notifyAll()信号后的执行顺序。因此,不相关的线程可能会启动exe
synchronized (object) {
if (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
synchronized (object) {
while (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
synchronized(queue) {
// producer produces inside the queue
queue.notify();
}
wait(mutex) {
// automatically release mutex
// and go on wait queue
// ... wait ... wait ... wait ...
// remove from queue
// re-acquire mutex
// exit the wait operation
}
synchronized(obj) {
if (!condition) {
obj.wait();
}
// Do some stuff related to condition
condition = false;
}
synchronized(obj) {
while (!condition) {
obj.wait();
}
// Do some stuff related to condition
condition = false;
}