javaconcurrencyjava.util.concurrent

Idiom for 2 argument version of Condition#await(long, TimeUnit)


What's the idiomatic way of handling spurious wakeups when using the 2 argument version of Condition#await(long, TimeUnit)

I understand the zero argument version:

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Scratch {
    private final Lock lock = new ReentrantLock();
    private final Condition flagSet = lock.newCondition();
    private boolean flag;

    void waitForever() throws InterruptedException {
        lock.lock();
        try {
            while (!flag) {
                flagSet.await();
            }
        } finally {
            lock.unlock();
        }
    }

    void setFlag() {
        lock.lock();
        try {
            flag = true;
            System.out.println("Flag set to true");
            flagSet.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        var scratch = new Scratch();
        var executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(scratch::setFlag, 10000, TimeUnit.MILLISECONDS);
        System.out.println("Waiting for flag...");
        scratch.waitForever();
        System.out.println("Finished waiting");
        executor.shutdownNow();
        System.exit(0);
    }
}

How would I change this so that I wait a maximum of 5 seconds while catering for the possibility of a spurious wakeup?


Solution

  • The way to do it would be to get the clock time in the at the start of the waitFor... method. Then when you return from await and the condition is not satisfied, get the clock time again and compare it with the first time to decide if you need to await again.

    The code could look something like this:

    void waitForUpToFiveSeconds() throws InterruptedException {
        lock.lock();
        long deadline = System.currentTimeMillis() + 5000L;
        try {
            flagSet.await(5000L, TimeUnit.MILLISECONDS);
            while (!flag) {
                long now = System.currentTimeMillis();
                if (now >= deadline) {
                    break;
                }
                flagSet.await(deadline - now, TimeUnit.MILLISECONDS);
            }
        } finally {
            lock.unlock();
        }
    }
    

    (It could probably be written in fewer lines of code, but I prefer to write code like this is a way that maximizes readability.)