为什么wait()总是在循环内调用

Gee*_*eek 65 java multithreading

我已经读过我们应该总是wait()在循环中调用a :

while (!condition) { obj.wait(); }
Run Code Online (Sandbox Code Playgroud)

没有循环它工作正常,为什么会这样?

aka*_*okd 71

您不仅需要循环它,还要检查循环中的条件.Java不保证您的线程只能通过notify()/ notifyAll()调用或正确的notify()/ notifyAll()调用来唤醒.由于此属性,无环版本可能适用于您的开发环境,并且会意外地在生产环境中失败.

例如,您正在等待某事:

synchronized (theObjectYouAreWaitingOn) {
   while (!carryOn) {
      theObjectYouAreWaitingOn.wait();
   }
}
Run Code Online (Sandbox Code Playgroud)

一个邪恶的线程出现了:

theObjectYouAreWaitingOn.notifyAll();
Run Code Online (Sandbox Code Playgroud)

如果邪恶的线程没有/不能搞砸carryOn你只是继续等待适当的客户端.

编辑:添加了一些样本.等待可以中断.它抛出InterruptedException,你可能需要在try-catch中包装wait.根据您的业务需求,您可以退出或取消异常并继续等待.

  • 这是正确的答案.等待的文档:http://java.sun.com/javase/6/docs/api/java/lang/Object.html#wait(long)...实际上描述了为什么需要将它放在循环中 - 虚假的醒来.OP读了吗? (5认同)

Tad*_*pec 37

它在Object.wait(long milis)的文档中得到了解答

线程也可以在没有被通知,中断或超时的情况下唤醒,即所谓的虚假唤醒.虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,并且如果条件不满足则继续等待.换句话说,等待应该总是出现在循环中,如下所示:

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait(timeout);
     ... // Perform action appropriate to condition
 }
Run Code Online (Sandbox Code Playgroud)

(有关该主题的更多信息,请参阅Doug Lea的"Java中的并发编程(第二版)"(Addison-Wesley,2000)中的第3.2.3节,或Joshua Bloch的"Effective Java Programming Language Guide"中的第50项(Addison-韦斯利,2001年).


Gra*_*ray 14

为什么wait()总是在循环内调用

while循环如此重要的主要原因是线程之间的竞争条件.当然,虚假的唤醒是真实的,对于某些架构来说它们很常见,但竞争条件更可能是while循环的原因.

例如:

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}
Run Code Online (Sandbox Code Playgroud)

使用上面的代码,可能有2个消费者线程.当生产者锁定queue要添加到它时,消费者#1可能在synchronized锁定时被阻塞,而消费者#2正在等待queue.当项目被添加到队列notify并由生产者调用时,#2将从等待队列中移动以在锁定上被阻止queue,但它将位于锁定已被阻止的#1使用者后面.这意味着,#1的消费者获得前进首先要求remove()queue.如果while循环只是一个if,那么当消费者#2在#1之后获得锁并调用时remove(),会发生异常,因为queue现在是空的 - 另一个消费者线程已经删除了该项.仅仅因为它被通知,它需要确保queue由于这种竞争条件仍然是空的.

这有据可查.这是我之前创建的一个网页,它详细解释了竞争条件,并提供了一些示例代码.


Phi*_*ipp 10

可能只有一个工人等待条件变为真实.

如果两个或更多工人醒来(notifyAll),他们必须再次检查条件.否则所有工人都会继续,即使其中一个人可能只有数据.


use*_*309 8

我想我得到了@Gray的回答.

让我试着为像我这样的新手重新说明,如果我错了,请求专家纠正我.

消费者同步块:

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}
Run Code Online (Sandbox Code Playgroud)

生产者同步块:

synchronized(queue) {
 // producer produces inside the queue
    queue.notify();
}
Run Code Online (Sandbox Code Playgroud)

假设以给定的顺序发生以下情况:

1)消费者#2进入消费者synchronized区块并等待,因为队列为空.

2)现在,生产者获得锁定queue并插入队列并调用notify().

现在,可以选择消费者#1运行,等待queue锁定synchronized首次进入该块

要么

消费者#2可以选择运行.

3)说,选择消费者#1继续执行.当它检查条件时,它将为真,它将remove()从队列中.

4)说,消费者#2正在从停止执行的地方开始(wait()方法之后的行).如果'while'条件不存在(而不是if条件),它将继续调用remove(),这可能导致异常/意外行为.


Aar*_*paa 5

因为wait和notify用于实现[条件变量](http://en.wikipedia.org/wiki/Monitor_ ( synchronization)#Blocking_condition_variables)所以你需要检查你正在等待的特定谓词是否为真持续.