Detailed Explanation of wait(), notify(), and notifyAll() Methods in Java
Description
wait(), notify(), and notifyAll() are low-level mechanisms in Java for inter-thread cooperation. They are defined in the Object class and must be used in conjunction with the synchronized keyword. These methods enable thread waiting and waking through an object's monitor lock, commonly used in producer-consumer models or conditional judgment scenarios.
1. Basic Function of the Methods
- wait(): Releases the lock held by the current thread and puts it into a waiting state until another thread calls
notify()ornotifyAll()on the same object, or until it is interrupted. - notify(): Randomly wakes up one thread that is waiting for the object's lock (if multiple threads are waiting, only one is awakened).
- notifyAll(): Wakes up all threads that are waiting for the object's lock; these threads will then compete for the lock.
Key Points:
- Before calling these methods, a thread must hold the object's monitor lock (i.e., it must be within a
synchronizedblock or method). - After calling
wait(), the thread releases the lock, allowing other threads to acquire the lock and execute.
2. Underlying Principle: Monitor Model
Every Java object has an associated monitor lock and a wait set.
- When a thread calls
wait(), it releases the lock and enters the wait set. - When another thread calls
notify(), the JVM moves one thread from the wait set to the entry set, giving it a chance to reacquire the lock. - The awakened thread must reacquire the lock before it can continue execution.
3. Usage Steps Example (Producer-Consumer Model)
The following code demonstrates a simple queue where consumers wait when the queue is empty and producers wait when the queue is full:
public class WaitNotifyExample {
private final Queue<String> queue = new LinkedList<>();
private final int MAX_SIZE = 5;
public synchronized void produce(String item) throws InterruptedException {
while (queue.size() == MAX_SIZE) {
wait(); // Queue is full, producer waits
}
queue.add(item);
System.out.println("Produced: " + item);
notifyAll(); // Wake up all waiting threads (consumers or producers)
}
public synchronized String consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // Queue is empty, consumer waits
}
String item = queue.poll();
System.out.println("Consumed: " + item);
notifyAll(); // Wake up other threads
return item;
}
}
Step-by-Step Analysis:
- Producer Thread:
- After acquiring the lock, checks if the queue is full; if full, calls
wait()to release the lock and wait. - After producing data, calls
notifyAll()to potentially wake up waiting consumers.
- After acquiring the lock, checks if the queue is full; if full, calls
- Consumer Thread:
- After acquiring the lock, checks if the queue is empty; if empty, calls
wait()to release the lock and wait. - After consuming data, calls
notifyAll()to potentially wake up waiting producers.
- After acquiring the lock, checks if the queue is empty; if empty, calls
Note:
- Must use
whileinstead ofifto check conditions to prevent spurious wakeups from causing incorrect state. - Calling
notifyAll()instead ofnotify()can avoid thread starvation (e.g., only waking up threads of the same type).
4. Common Issues and Precautions
- IllegalMonitorStateException: Thrown when these methods are called outside a synchronized block.
- Interruption Handling: The
wait()method may be interrupted byinterrupt();InterruptedExceptionmust be caught. - Timeout Wait: Use
wait(long timeout)to avoid indefinite waiting. - Comparison with
LockandCondition: TheConditionin JUC provides a more flexible waiting/waking mechanism (e.g., multiple condition queues).
5. Summary
wait()/notify() is the foundation of Java multi-thread cooperation, with core principles:
- Achieving thread synchronization through object monitor locks;
- Using
whileloops to avoid spurious wakeups; - Coordinating state dependencies between threads.
Mastering these methods helps in understanding the underlying implementation of more advanced concurrency tools (e.g.,BlockingQueue).