How to use TimeUnit.timedWait() without losing nanosecond precision?

1.9k Views Asked by At

I'm trying to implement Future.get(long, TimeUnit) in terms of TimeUnit.timedWait(Object, long).

It's not clear how to use TimeUnit.timedWait(Object, long) in a way that handles spurious wakeups without losing the nanosecond component of TimeUnit. Normally you'd do something like this:

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);
  }
}

but you lose the nanosecond component. If everyone is simply dropping the nanosecond component then what is the point of TimeUnit even supporting nanoseconds and offering TimeUnit.timedWait()?

3

There are 3 best solutions below

0
On BEST ANSWER

CountDownLatch seems to be the easiest way to implement this:

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 is not vulnerable to spurious wakeups (because it can check the latch state internally before returning).

3
On

Before you wait, store the time at which you want to timeout.

Before you notify the waiting thread, set some shared (synchronized) state signalling that the waiting thread should stop waiting because the computation is complete.

When your thread wakes up from the wait for whatever reason it should check the shared state to see if it should stop waiting, and it should also check how long is left before the timeout expires. If the timeout has not expired, and the shared state tells does not say to stop waiting then you should wait again (but using a new shorter time out calculated from the current time).

1
On

The answer to your question lies in the Object.wait(long) spec:

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).