Condition.await releases the associated lock while waiting and must return to a predicate check before work proceeds.
Java Condition.await: recheck the predicate after every signal
A signal is not the state
The producer first signals without making inventory available. The consumer wakes, sees the predicate is still false, and waits again. Only the second signal follows a real state change. This deliberate false signal tests the same loop needed for spurious wakeups and competing consumers.
The condition is bound to one lock. Every predicate read and update happens under that lock, so the signal and state change have one ownership rule. Condition basics cover lock acquisition; BlockingQueue is simpler when the shared state is just a queue.
Bound the wait when needed
An unbounded await can leave a worker parked forever if the producer dies. Use a timed wait with a monotonic deadline and a cancellation path for requests with service-level limits. Never treat one wakeup as proof that the business condition holds.
Working program
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class InventoryReadyCondition {
public static void main(String[] args) throws InterruptedException {
ReentrantLock guard = new ReentrantLock();
Condition changed = guard.newCondition();
CountDownLatch waiting = new CountDownLatch(1);
CountDownLatch falseSignalSeen = new CountDownLatch(1);
AtomicInteger wakeups = new AtomicInteger();
boolean[] ready = {false};
Thread consumer = new Thread(() -> {
guard.lock();
try {
waiting.countDown();
while (!ready[0]) {
changed.await();
if (wakeups.incrementAndGet() == 1) falseSignalSeen.countDown();
}
} catch (InterruptedException stopped) { Thread.currentThread().interrupt(); }
finally { guard.unlock(); }
});
consumer.start();
waiting.await();
guard.lock();
try { changed.signal(); }
finally { guard.unlock(); }
falseSignalSeen.await();
guard.lock();
try { ready[0] = true; changed.signal(); }
finally { guard.unlock(); }
consumer.join();
System.out.println("wakeups=" + wakeups.get());
System.out.println("ready=" + ready[0]);
}
}Output
wakeups=2
ready=trueCost and ownership
The fixture uses one lock, one condition, and constant state. Real waiting time is unbounded unless a deadline is added. The predicate loop is necessary even when a signal is deliberate, because another consumer may change the state first.
Common Mistakes
- Do not replace while with if around await.
- Do not signal without holding the associated lock when the predicate update needs the same ownership rule.
- Do not treat signal as a durable record of available work.
Read next
Java conditions: protected predicates and timed waiting, Java ReentrantLock: protect a complete state transition, Java blocking queues: bounded capacity and backpressure, Java lockInterruptibly: cancel a worker waiting for ownership.
