foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • quiz
Java
  • Master the wait/notify/notifyAll mechanism
  • Understand why to use while loops with wait()
  • Implement the producer-consumer pattern
  • Know the modern alternatives in java.util.concurrent

Thread Communication: wait() and notify()

Synchronization prevents threads from interfering. But sometimes threads need to cooperate. Producer creates data, consumer processes it. Reader waits for writer. This needs thread communication—threads signaling each other.

The wait/notify Mechanism

Java provides three methods on every object for thread communication:

  • wait() - Releases the lock and waits for notification
  • notify() - Wakes up one waiting thread
  • notifyAll() - Wakes up all waiting threads

Must be called from within a synchronized block on the same object.

Basic wait/notify Pattern

public class MessageQueue {
    private String message;
    private boolean hasMessage = false;
    
    public synchronized void put(String msg) {
        // Wait while there's already a message
        while (hasMessage) {
            try {
                wait();  // Release lock and wait
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        
        // Store the message
        message = msg;
        hasMessage = true;
        
        // Notify waiting consumer
        notify();
    }
    
    public synchronized String take() {
        // Wait while there's no message
        while (!hasMessage) {
            try {
                wait();  // Release lock and wait
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        
        // Get the message
        String msg = message;
        hasMessage = false;
        
        // Notify waiting producer
        notify();
        return msg;
    }
}

How wait() Works

When a thread calls wait():

  1. Releases the lock on the object
  2. Enters WAITING state
  3. Waits until another thread calls notify()/notifyAll() on the same object
  4. Re-acquires the lock before returning from wait()
  5. Continues execution after the wait() call
synchronized (lock) {
    // 1. Thread holds the lock here
    
    while (!condition) {
        lock.wait();  // 2. Releases lock, waits
                      // 4. Re-acquires lock when notified
    }
    
    // 5. Thread holds the lock again, condition is true
}

Why Use while Instead of if?

Always use while, never if:

// WRONG - using if
synchronized (lock) {
    if (!condition) {
        wait();
    }
    // Condition might be false again! (spurious wakeup)
}

// CORRECT - using while
synchronized (lock) {
    while (!condition) {
        wait();
    }
    // Guaranteed: condition is true
}

Reasons:

  1. Spurious wakeups: A thread may wake up without being notified
  2. Multiple waiters: Another thread might consume the condition first
  3. notifyAll(): All threads wake up, but only one should proceed

notify() vs notifyAll()

Method Behavior Use When
notify() Wakes one waiting thread (arbitrary) Only one waiter can proceed
notifyAll() Wakes all waiting threads Multiple waiters might proceed, or conditions differ

Best practice: Prefer notifyAll() for safety. It's less efficient but prevents subtle bugs.

// Use notifyAll when waiters have different conditions
public synchronized void finishWriting() {
    writingComplete = true;
    notifyAll();  // All readers should wake up
}

// notify() is OK when all waiters are equivalent
public synchronized void addItem(Item item) {
    queue.add(item);
    notify();  // Any one consumer can handle it
}

Producer-Consumer Example

A classic pattern where producers create items and consumers process them:

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity;
    
    public ProducerConsumer(int capacity) {
        this.capacity = capacity;
    }
    
    public synchronized void produce(int value) throws InterruptedException {
        // Wait if queue is full
        while (queue.size() == capacity) {
            System.out.println("Queue full, producer waiting...");
            wait();
        }
        
        queue.add(value);
        System.out.println("Produced: " + value);
        
        // Notify consumers
        notifyAll();
    }
    
    public synchronized int consume() throws InterruptedException {
        // Wait if queue is empty
        while (queue.isEmpty()) {
            System.out.println("Queue empty, consumer waiting...");
            wait();
        }
        
        int value = queue.poll();
        System.out.println("Consumed: " + value);
        
        // Notify producers
        notifyAll();
        return value;
    }
}

// Usage
public static void main(String[] args) {
    ProducerConsumer pc = new ProducerConsumer(5);
    
    // Producer thread
    Thread producer = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            try {
                pc.produce(i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break;
            }
        }
    });
    
    // Consumer thread
    Thread consumer = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            try {
                pc.consume();
                Thread.sleep(200);  // Slower than producer
            } catch (InterruptedException e) {
                break;
            }
        }
    });
    
    producer.start();
    consumer.start();
}

wait() with Timeout

You can specify a maximum wait time:

synchronized (lock) {
    long deadline = System.currentTimeMillis() + 5000;  // 5 seconds from now
    
    while (!condition) {
        long remaining = deadline - System.currentTimeMillis();
        if (remaining <= 0) {
            throw new TimeoutException("Timed out waiting for condition");
        }
        wait(remaining);
    }
}

Common Patterns

1. One-Shot Latch

A gate that opens once and stays open:

public class Latch {
    private boolean open = false;
    
    public synchronized void open() {
        open = true;
        notifyAll();  // Wake all waiting threads
    }
    
    public synchronized void await() throws InterruptedException {
        while (!open) {
            wait();
        }
    }
}

2. Barrier

Wait until N threads arrive:

public class SimpleBarrier {
    private final int parties;
    private int count = 0;
    
    public SimpleBarrier(int parties) {
        this.parties = parties;
    }
    
    public synchronized void await() throws InterruptedException {
        count++;
        if (count < parties) {
            // Not everyone is here yet, wait
            while (count < parties) {
                wait();
            }
        } else {
            // Everyone is here, wake them up
            notifyAll();
        }
    }
}

3. Condition Variable

Wait for a specific condition:

public class BoundedBuffer<T> {
    private final Object[] items;
    private int count, putIndex, takeIndex;
    
    public BoundedBuffer(int capacity) {
        items = new Object[capacity];
    }
    
    public synchronized void put(T item) throws InterruptedException {
        while (count == items.length) {  // Buffer full
            wait();
        }
        items[putIndex] = item;
        putIndex = (putIndex + 1) % items.length;
        count++;
        notifyAll();
    }
    
    @SuppressWarnings("unchecked")
    public synchronized T take() throws InterruptedException {
        while (count == 0) {  // Buffer empty
            wait();
        }
        T item = (T) items[takeIndex];
        takeIndex = (takeIndex + 1) % items.length;
        count--;
        notifyAll();
        return item;
    }
}

Common Mistakes

1. Calling wait/notify Outside Synchronized Block

// WRONG - throws IllegalMonitorStateException
lock.wait();

// CORRECT
synchronized (lock) {
    lock.wait();
}

2. Synchronizing on Different Objects

// WRONG - wait and notify on different locks
synchronized (lock1) {
    lock2.wait();  // IllegalMonitorStateException!
}

// CORRECT - same object for synchronized and wait
synchronized (lock) {
    lock.wait();
}

3. Using if Instead of while

// WRONG - may proceed when condition is false
if (!ready) wait();

// CORRECT - always rechecks condition
while (!ready) wait();

Modern Alternatives

Java provides higher-level abstractions in java.util.concurrent:

Pattern wait/notify Modern Alternative
Latch Custom implementation CountDownLatch
Barrier Custom implementation CyclicBarrier
Queue Manual synchronization BlockingQueue
Condition wait()/notify() Condition with Lock
// Modern producer-consumer with BlockingQueue
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

// Producer
queue.put(value);  // Blocks if full

// Consumer
Integer value = queue.take();  // Blocks if empty

Thread communication: wait() releases lock and waits for notification, notify() wakes one waiting thread, notifyAll() wakes all. Must be called within synchronized block on the same object. Always use while to check conditions, never if. Prefer notifyAll() over notify() for safety. wait() releases the lock, returns after re-acquiring it. Consider java.util.concurrent classes for production code.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service