Condition Interface in Java
The Condition interface in java.util.concurrent.locks is the Lock version of wait() and notify(). You get a Condition from a lock with lock.newCondition(), and threads call await() to wait and signal() or signalAll() to wake waiters. Its big advantage is that one lock can have several conditions, such as "not full" and "not empty", so you can wake exactly the threads that can make progress. In this lesson, you will learn its methods and rebuild the bounded buffer with two conditions.
Methods
| Method | Description |
|---|---|
await() | It is used to release the lock and wait until signaled (like wait()). |
await(time, unit) | It waits at most the given time. |
awaitUninterruptibly() | It waits and ignores interrupts. |
signal() | It wakes one waiting thread (like notify()). |
signalAll() | It wakes all waiting threads (like notifyAll()). |
As with wait, the thread must hold the lock when calling these methods, and should await() in a while loop.
Example: A Bounded Buffer with Two Conditions
Let's see an example where producers wait on a notFull condition and consumers wait on a notEmpty condition.
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBuffer {
private final Queue<Integer> items = new ArrayDeque<>();
private final int capacity = 2;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
void put(int x) throws InterruptedException {
lock.lock();
try {
while (items.size() == capacity) {
notFull.await();
}
items.add(x);
notEmpty.signal();
} finally {
lock.unlock();
}
}
int take() throws InterruptedException {
lock.lock();
try {
while (items.isEmpty()) {
notEmpty.await();
}
int x = items.remove();
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionBuffer buffer = new ConditionBuffer();
int[] sum = new int[1];
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) buffer.put(i);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) sum[0] += buffer.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
producer.join();
consumer.join();
System.out.println("Advitha's consumer received a total of " + sum[0]);
}
}Advitha's consumer received a total of 55Explanation: The producer waits on notFull while the buffer holds two items; the consumer waits on notEmpty while it is empty. After adding, the producer signals only notEmpty, waking a consumer; after removing, the consumer signals only notFull, waking a producer. All ten numbers (1 to 10, totaling 55) arrive. With plain wait/notify, both kinds of thread share one wait set, so notifyAll must be used and wakes threads that can't proceed.
How BlockingQueue Uses Conditions
Common Mistakes
Calling await() or signal() without holding the lock. It throws IllegalMonitorStateException.
Calling wait() on a Condition object. That's Object.wait; use await().
Using if instead of while around await(). Spurious wakeups apply here too.
Practice
- Easy: Add a
size()method toConditionBufferthat takes the lock and returns the number of items. - Harder: Run two producers (each putting 1 to 10) and two consumers (each taking 10 items) on the buffer, and check the total is 110.
Key Takeaways
Was this tutorial helpful?