Search code examples
javamultithreadingwait

Java: Object.wait(long) broken


It seems to me as if the timeout version of Object.wait is rarely usable as-is. This is because:

  • The method does not handle spurious wake-ups.
  • The method does not signal whether it returned due to a notify or a timeout.

The C++ version with the predicate as parameter seems to make it right. A corresponding Java method in Object with signature

boolean wait(long timeOutMillis, BooleanSupplier condition)

could be used conveniently as follows:

Object obj = ...;
BooleanSupplier condition = ...;
synchronized (obj) {
    if (obj.wait(1000, condition)) {
        // condition is fulfilled
    } else {
        // timeout happened
    }
}

As a workaround, I could use the following ugly helper method:

public static boolean safeWait(Object waitObject, long timeOutMillis, BooleanSupplier condition) throws InterruptedException {
    if (condition.getAsBoolean()) {
        return true;
    }
    long rest = timeOutMillis;
    while (true) {
        long t0 = System.currentTimeMillis();
        waitObject.wait(rest);
        long t1 = System.currentTimeMillis();
        long waited = t1 - t0;
        if (condition.getAsBoolean()) {
            return true;
        }
        rest = rest - waited;
        if (rest <= 0) {
            return false;
        }
    }
}

I better should formulate some questions:

  • Am I right, that it's broken?
  • Why don't they fix this?
  • Is there a better way to work around the problem?

Solution

  • Your safeWait(...) looks good in principle, but you could streamline it a bit:

    public static boolean safeWait(Object waitObject, long timeOutMillis, BooleanSupplier condition)
        throws InterruptedException 
    {
        long now = System.currentTimeMillis();
        long end_time = now + timeOutMillis;
    
        while (! condition.getAsBoolean()) {
            if (now > end_time) {
                return false;
            }
            waitObject.wait(end_time - now);
            now = System.currentTimeMillis();
        }
    
        return true;
    }