Let’s compare wait()/notify() (used with synchronized) and Condition (used with ReentrantLock) — both are used for thread coordination, but Condition gives you more flexibility.
We’ll walk through:
- A
wait()/notify()example - A
Conditionexample doing the same thing
Scenario: We have a producer and consumer. The producer sets a flag when data is ready; the consumer waits until that happens.
🧵 1. wait()/notify() Example with synchronized
class SharedData {
private boolean ready = false;
public synchronized void waitForData() {
while (!ready) {
try {
wait(); // 💤 releases monitor and waits
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Consumer: data is ready!");
}
public synchronized void produceData() {
ready = true;
notify(); // 🚨 wakes one waiting thread
System.out.println("Producer: data produced!");
}
}
public class Main {
public static void main(String[] args) {
SharedData data = new SharedData();
Thread consumer = new Thread(data::waitForData);
Thread producer = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
data.produceData();
});
consumer.start();
producer.start();
}
}
✅ Output:
Producer: data produced!
Consumer: data is ready!
🔐 2. Same Logic Using Condition + ReentrantLock
import java.util.concurrent.locks.*;
class SharedDataWithLock {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void waitForData() {
lock.lock();
try {
while (!ready) {
condition.await(); // 💤 releases lock and waits
}
System.out.println("Consumer: data is ready!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void produceData() {
lock.lock();
try {
ready = true;
condition.signal(); // 🚨 wakes one waiting thread
System.out.println("Producer: data produced!");
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
SharedDataWithLock data = new SharedDataWithLock();
Thread consumer = new Thread(data::waitForData);
Thread producer = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
data.produceData();
});
consumer.start();
producer.start();
}
}
✅ Output is the same, but…
🧠 Why Use Condition Over wait()/notify()?
| Feature | wait()/notify() | Condition (with Lock) |
|---|---|---|
| Number of condition queues | 1 per monitor | ✅ Multiple possible |
| Targeted signaling | ❌ No (notify()) | ✅ Yes (condition.signal()) |
| Locking flexibility | Only intrinsic locks (synchronized) | ✅ Any Lock (like ReentrantLock) |
| Reentrant support | ✅ Yes | ✅ Yes |
| Fairness, timeout control | Limited | ✅ Full support |
🧩 Summary
- Use
wait()/notify()for simple coordination inside synchronized blocks - Use
Conditionif you need:- Multiple wait conditions
- Better signaling control
- Integration with
ReentrantLock,tryLock, or advanced behavior