Understanding wait() and notify() in Java Multithreading
This article explains the concepts, usage conditions, execution flow, internal mechanisms, and best‑practice guidelines for Java's wait() and notify() methods, illustrated with a producer‑consumer example and code snippets for safe thread coordination.
In Java multithreaded programming, thread cooperation and communication are essential for building high‑concurrency systems, and the Object class methods wait() and notify() implement this via the monitor lock mechanism.
Basic concepts and prerequisites : wait() puts the current thread into a waiting state and releases the object lock, requiring another thread to call notify() or notifyAll() on the same object to wake it. notify() wakes a single waiting thread (chosen randomly), while notifyAll() wakes all. Both methods must be called inside a synchronized block or method, otherwise an IllegalMonitorStateException is thrown.
Typical scenarios include the producer‑consumer model, where a producing thread notifies a consuming thread after producing data, and general thread synchronization where a thread must wait for a condition (e.g., resource readiness) before proceeding.
Execution flow of wait() :
1. Acquire the lock before calling wait() .
2. Release the lock and enter the Wait Set.
3. Remain in WAITING (or timed waiting) until notified or timed out.
4. After being notified, move to the Entry List and compete for the lock again.
Execution flow of notify() :
1. The calling thread must hold the lock.
2. Randomly select one waiting thread (or all with notifyAll() ) to move to the Entry List.
3. The lock is released only after the notifying thread exits the synchronized block, allowing awakened threads to compete for the lock.
Internal mechanisms involve the monitor lock, the Wait Set (stores threads that called wait() ) and the Entry List (threads waiting to reacquire the lock). The wake‑up process is nondeterministic; notify() selects a thread at random, and spurious wake‑ups can occur, so conditions should be re‑checked in a loop.
Code example – Producer‑Consumer model :
public class ProducerConsumer {
private final Object lock = new Object();
private int count = 0;
private final int LIMIT = 10;
void produce() throws InterruptedException {
synchronized (lock) {
while (count >= LIMIT) {
lock.wait(); // wait for consumer
}
count++;
System.out.println("Produced: " + count);
lock.notifyAll(); // wake consumers
}
}
void consume() throws InterruptedException {
synchronized (lock) {
while (count <= 0) {
lock.wait(); // wait for producer
}
count--;
System.out.println("Consumed: " + count);
lock.notifyAll(); // wake producers
}
}
}Key points :
Use wait() together with notifyAll() to avoid deadlock.
Check conditions in a while loop to guard against spurious wake‑ups.
Keep synchronized blocks as small as possible to reduce lock contention.
Handle InterruptedException properly, e.g.:
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt status
}Choose the appropriate method: notify() for single‑thread scenarios, notifyAll() for multiple‑thread cooperation, and avoid nested locks to prevent deadlocks.
Conclusion : wait() and notify() enable efficient thread communication through monitor locks, but must be used with strict synchronization rules; for more complex cases, higher‑level utilities from java.util.concurrent (such as Lock and Condition ) can provide better control.
Cognitive Technology Team
Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.