Adam Arold
Adam Arold

Reputation: 30548

How to await a Condition without the chance of await being called after signal in Java?

I have a wait/notify mechanism in my code which basically wraps Lock and Condition like this:

class ConditionWithTimeout { // timeout part omitted
    private Lock lock = new ReentrantLock();
    private Condition cond = lock.newCondition();

    public void doWait() throws InterruptedException {
        lock.lock();
        cond.await();
        lock.unlock();
    }

    public void doNotify() {
        lock.lock();
        cond.signalAll();
        lock.unlock();
    }
}

My problem is that if I do something like this:

someFunction();
myCond.doWait();

it is possible that if someFunction calls doNotify at some point on myCondition I will wait until myCondition times out because doWait in someFunction might be executed before this code jumps to the next line to execute myCond.doWait.

I worked around it by adding a preWaitFn:

class ConditionWithTimeout {
    private Lock lock = new ReentrantLock();
    private Condition cond = lock.newCondition();
    private Executor hookExecutor = Executors.newSingleThreadExecutor();

    public void doWait() throws InterruptedException {
        doWait(() -> {
        });
    }

    public void doWait(Runnable preWaitFn) throws InterruptedException {
        lock.lock();
        hookExecutor.execute(preWaitFn);
        cond.await();
        lock.unlock();
    }

    public void doNotify() {
        lock.lock();
        cond.signalAll();
        lock.unlock();
    }
}

and it works now but this seems like a code smell to me because it is still possible that doNotify is called before cond.await is called.

So my question is that what is the best practice in such situations? I need to block until someFunction finishes its work but I wish to avoid such corner cases as this.

Upvotes: 0

Views: 321

Answers (2)

Sbodd
Sbodd

Reputation: 11454

You could try using a CountDownLatch. Depending on your use case, this may not be the best answer - it works if you can create a new instance of your flag object for each call, but not if you need to reuse one. The linked Javadoc page contains examples of how to use it. This is basically a off-the-shelf replacement for shmosel's answer.

Upvotes: 1

shmosel
shmosel

Reputation: 50726

The usual pattern is to check some logical condition and use the signal as a means of notifying another thread that the condition has been updated. In your case, a simple flag should suffice:

private boolean flag = false;

public void doWait() throws InterruptedException {
    lock.lock();
    try {
        // Loop is necessary to avoid spurious wakeup
        while (!flag) {
            cond.await();
        }
    } finally {
        // Unlock in finally in case exception is thrown
        lock.unlock();
    }
}

public void doNotify() {
    lock.lock();
    try {
        flag = true;
        cond.signalAll();
    } finally {
        lock.unlock();
    }
}

Upvotes: 2

Related Questions