Detailed Explanation of wait(), notify(), and notifyAll() Methods in Java

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() or notifyAll() 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 synchronized block 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:

  1. 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.
  2. 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.

Note:

  • Must use while instead of if to check conditions to prevent spurious wakeups from causing incorrect state.
  • Calling notifyAll() instead of notify() 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 by interrupt(); InterruptedException must be caught.
  • Timeout Wait: Use wait(long timeout) to avoid indefinite waiting.
  • Comparison with Lock and Condition: The Condition in 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 while loops 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).