wait/notify and Condition

Multithreading · lesson 7 of 38 · 4 min read

Make one thread wait for another to change state, without burning CPU in a spin loop.

Open this lesson in the learning hub

Key points

  • wait() releases the monitor and parks the thread. notify() wakes one waiter, notifyAll() wakes them all.
  • You must already hold the monitor to call them, or you get IllegalMonitorStateException.
  • Always wait inside a while loop, never an if. Spurious wakeups are legal, and another thread may grab the state first.
  • Prefer notifyAll() unless you can prove every waiter wants the same thing. notify() can wake the wrong one and stall.
  • Condition is the Lock equivalent: await() and signal(). One lock can carry several conditions, like notFull and notEmpty.
  • Both are low-level plumbing. Try BlockingQueue, a latch or a future before writing your own.

Example

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Object monitor = new Object();
        boolean[] ready = { false };

        Thread waiter = new Thread(() -> {
            synchronized (monitor) {
                while (!ready[0]) {                  // ALWAYS a while loop, never an if
                    try { monitor.wait(); } catch (InterruptedException e) { return; }
                }
                System.out.println("wait/notify : woke up, condition is true");
            }
        });
        waiter.start();
        Thread.sleep(50);
        synchronized (monitor) { ready[0] = true; monitor.notifyAll(); }
        waiter.join();

        Box box = new Box();
        Thread consumer = new Thread(() -> {
            try { System.out.println("condition   : took " + box.take()); }
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        });
        consumer.start();
        Thread.sleep(50);
        box.put("apple");
        consumer.join();
    }

    static class Box {
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition notEmpty = lock.newCondition();
        private final Queue<String> items = new ArrayDeque<>();

        void put(String item) {
            lock.lock();
            try { items.add(item); notEmpty.signal(); } finally { lock.unlock(); }
        }

        String take() throws InterruptedException {
            lock.lock();
            try {
                while (items.isEmpty()) notEmpty.await();
                return items.remove();
            } finally { lock.unlock(); }
        }
    }
}

Wait in a while loop, or you will eventually ship a very strange bug.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.