我正在尝试用Future.get(long, TimeUnit)来实现TimeUnit.timedWait(Object, long)。
目前尚不清楚如何以一种处理虚假唤醒的方式使用TimeUnit.timedWait(Object, long)
而不会丢失TimeUnit的纳秒组件。通常你会做这样的事情:
public V get(long timeout, TimeUnit unit)
{
long expirationTime = new Date().getTime() + unit.toMillis(timeout);
while (!condition)
{
long timeLeft = expirationTime - new Date().getTime();
if (timeLeft <= 0)
throw new TimeoutException();
unit.timedWait(object, timeLeft);
}
}
但你失去了纳秒组件。如果每个人都只是放弃纳秒组件,那么TimeUnit
甚至支持纳秒并提供TimeUnit.timedWait()
是什么意思?
答案 0 :(得分:3)
在等待之前,请存储您想要超时的时间。
在通知等待线程之前,设置一些共享(同步)状态信号,表明等待线程应该因为计算完成而停止等待。
当你的线程因为某种原因从等待中醒来时它应该检查共享状态以查看它是否应该停止等待,并且它还应检查超时到期之前剩余的时间。如果超时未到期,并且共享状态告知不要说停止等待,那么您应该再次等待(但是使用从当前时间计算的新的较短时间)。
答案 1 :(得分:1)
您的问题的答案在于Object.wait(long)规范:
线程也可以在没有被通知,中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,并且如果条件不满足则继续等待。换句话说,等待应该总是出现在循环中,如下所示:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
(有关该主题的更多信息,请参阅Doug Lea的“Java中的并发编程(第二版)”(Addison-Wesley,2000)中的第3.2.3节,或Joshua Bloch的“Effective Java Programming Language Guide”中的第50项。 (Addison-Wesley,2001)。
答案 2 :(得分:1)
CountDownLatch似乎是实现此目的的最简单方法:
public class MutableFuture<T> implements Future<T>
{
private final CountDownLatch done = new CountDownLatch(1);
private T value;
private Throwable throwable;
public synchronized boolean isDone()
{
return done.getCount() == 0;
}
public synchronized T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
{
if (!done.await(timeout, unit))
throw new TimeoutException();
if (throwable != null)
throw new ExecutionException(throwable);
return value;
}
// remaining methods left as exercise to the reader :)
}
CountdownLatch不容易受到spurious wakeups的攻击(因为它可以在返回之前检查内部的锁存状态。)