问题 在while循环中等待(长时间超时)?


我读过你应该放的 Object.wait() 在while循环中调用Java。原因是这个线程可能被唤醒,你等待通知的条件仍然是假的(虚假的唤醒)。

关于什么 Object.wait(long timeout)。在这里,您不希望循环该条件,因为您希望它在指定的时间后超时。但是如果你不把它放在一个循环中那么你怎么能确保它不会被提前唤醒呢?


8252
2017-10-24 20:32


起源



答案:


但是如果你不把它放在一个循环中那么你怎么能确保它不会被提前唤醒呢?

这是Java IMO的一个缺陷,尽管它可能是各种OS变量中底层线程支持的缺陷。我怀疑Java知道等待是否超时,但是如果不重新测试条件并专门测试时间,调用者就无法弄明白。丑陋。

所以你需要把它 wait(long timeout) 在一个 while 循环以及  测试时间是否超过超时时间。我知道没有别的方法可以做到这一点。

long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
while (!condition) {
    long waitMs = timeoutExpiredMs - System.currentTimeMillis();
    if (waitMs <= 0) {
       // timeout expired
       break;
    }
    // we assume we are in a synchronized (object) here
    object.wait(waitMs);
    // we might get improperly awoken here so we loop around to see if we timed out
}

11
2017-10-24 20:35



不确定这是一个缺陷。假设我们知道唤醒的确切原因,你能用它做什么?无论如何我们必须重新检查条件。 - irreputable
如果wait返回一个布尔值@irreputable,那么你至少不需要测试到期时间。你知道你是否得到了通知。 - Gray
即使唤醒是由于超时,条件仍然可以成为现实。既然我们希望这里的条件成立,我们应该重新检查它(检查通常很便宜)。 - irreputable
我当然知道“假的唤醒”@PavelZdenek。我所说的缺点是无能为力 wait(timeout) 报告是否超时的方法。我怀疑它知道。顺便说一句,每个人都谈到虚假的唤醒,但真正的原因 while 循环是生产者/消费者竞争条件。看这里: 256.com/gray/docs/misc/producer_consumer_race_conditions - Gray
你的代码中有一个catch,当计算的差异变为负数时,object.wait(...)会抛出IllegalArgumentException。例如,如果在timeoutExpiredMs - 5发生虚假超时,超时检查发生在timeoutExpiredMs之前,条件为假并且直到下一个object.wait(...)被执行,超过5ms通过(例如,另一个线程被调度),则会发生这种情况。 ) - clausavram


答案:


但是如果你不把它放在一个循环中那么你怎么能确保它不会被提前唤醒呢?

这是Java IMO的一个缺陷,尽管它可能是各种OS变量中底层线程支持的缺陷。我怀疑Java知道等待是否超时,但是如果不重新测试条件并专门测试时间,调用者就无法弄明白。丑陋。

所以你需要把它 wait(long timeout) 在一个 while 循环以及  测试时间是否超过超时时间。我知道没有别的方法可以做到这一点。

long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
while (!condition) {
    long waitMs = timeoutExpiredMs - System.currentTimeMillis();
    if (waitMs <= 0) {
       // timeout expired
       break;
    }
    // we assume we are in a synchronized (object) here
    object.wait(waitMs);
    // we might get improperly awoken here so we loop around to see if we timed out
}

11
2017-10-24 20:35



不确定这是一个缺陷。假设我们知道唤醒的确切原因,你能用它做什么?无论如何我们必须重新检查条件。 - irreputable
如果wait返回一个布尔值@irreputable,那么你至少不需要测试到期时间。你知道你是否得到了通知。 - Gray
即使唤醒是由于超时,条件仍然可以成为现实。既然我们希望这里的条件成立,我们应该重新检查它(检查通常很便宜)。 - irreputable
我当然知道“假的唤醒”@PavelZdenek。我所说的缺点是无能为力 wait(timeout) 报告是否超时的方法。我怀疑它知道。顺便说一句,每个人都谈到虚假的唤醒,但真正的原因 while 循环是生产者/消费者竞争条件。看这里: 256.com/gray/docs/misc/producer_consumer_race_conditions - Gray
你的代码中有一个catch,当计算的差异变为负数时,object.wait(...)会抛出IllegalArgumentException。例如,如果在timeoutExpiredMs - 5发生虚假超时,超时检查发生在timeoutExpiredMs之前,条件为假并且直到下一个object.wait(...)被执行,超过5ms通过(例如,另一个线程被调度),则会发生这种情况。 ) - clausavram


long deadline = now() + timeout;

synchronized(lock)

    while( !condition() && now()<deadline )
        lock.wait( deadline - now() );

    if(condition())
        ...
    else // timeout
        ...

2
2017-10-24 20:37





这是因为java有Mesa风格的显示器而不是Hoare风格的显示器。所以你需要等待一段时间。 请搜索字符串“因此,通常需要在下面的网页中将每个等待操作包含在这样的循环中”,

http://en.wikipedia.org/wiki/Monitor_(synchronization)#Nonblocking_condition_variables

如果它是Hoare风格的显示器,那么你可以放弃等待。我将很快添加Mesa监视器的详细信息。这不是Java的缺陷。两种类型的监视器都有优点和缺点。


2
2017-09-20 21:15





在循环中调用等待不仅仅是为了处理偶尔的虚假唤醒。在一般(非玩具示例)情况下,多个线程争用锁定,当一个线程从等待中醒来时,它在等待之前所做的任何检查都不足以预测该对象所处的状态。那个等待的人已经放弃了锁定,所以从那时起就发生了任何事情,而且关于通知如何工作没有任何原子,只是因为你得到通知并不意味着另一个线程没有在通知发出之间的时间内潜入并且通知的线程重新获得锁定。

从根本上说,你需要在循环中等待,因为你需要通过锁定来检查当前状态,以便能够分辨出正在发生的事情。超时不会改变这一点。超时是一种安全机制,因此如果错过通知,线程将永远不会挂起。如果等待时间通常不需要任何特定操作,只需重新获取锁定,继续循环体,并像往常一样检查thr条件。

这不是Java的错,这就是pthreads的工作方式。


0
2017-07-25 13:45