🧵 Concurrency Fundamentals · Advanced

wait / notify in Java

Condition waiting inside synchronized, spurious wakeups, while-loop guard.

🧩 The mysteryA consumer must wait until the queue has an item. Spinning burns a core; sleeping wastes time. Java's oldest answer is wait() and notify(), with a famous trap hiding in a single word.

Sleep until something changes

obj.wait() releases obj's monitor and suspends the thread until another thread calls notify() or notifyAll() on the same object. Releasing matters: it lets other threads enter and change the state. Before wait() returns, the thread re-acquires the lock.

synchronized (queue) {
    while (queue.isEmpty()) {
        queue.wait();
    }
    return queue.remove();
}

You must hold the lock

wait(), notify() and notifyAll() may only be called by a thread that owns that object's monitor, i.e. inside synchronized (obj). Otherwise they throw IllegalMonitorStateException. It doesn't matter whether anyone is waiting.

🔮 Predict it

Your turn

No synchronized block in sight. What happens?

Object lock = new Object();
lock.wait();
  1. Throws IllegalMonitorStateException
  2. Waits forever
  3. Compile error
Show the answer

It throws IllegalMonitorStateException straight away. main doesn't own lock's monitor, so it isn't allowed to wait on it. The same goes for notify().

while, never if

✗ if
synchronized (queue) {
    if (queue.isEmpty()) {
        queue.wait();
    }
    return queue.remove();
}

After waking, the queue may be empty again: another consumer got there first, or the wakeup was spurious. remove() then crashes.

✓ while
synchronized (queue) {
    while (queue.isEmpty()) {
        queue.wait();
    }
    return queue.remove();
}

Re-checks the condition after every wakeup, so it only proceeds when there really is an item.

notify() vs notifyAll()

notify() wakes one arbitrary waiter, which may be waiting for a *different* condition. If it can't proceed, the signal is lost and others may wait forever. notifyAll() wakes everyone; each re-checks its own condition in its while loop. Prefer notifyAll() unless one waiter is surely enough.

🤔 Think first

Why not sleep?

Thread A waits for done inside synchronized (this). Why use wait() there instead of Thread.sleep(10) in the loop?

Think about it, then reveal the answer

sleep() keeps holding the lock, so thread B, which must enter synchronized (this) to set done = true, can never get in. wait() releases the monitor, letting B in, and wakes up when notified instead of polling.

💼 In the real world

In practice

Modern code rarely calls wait/notify directly: BlockingQueue, CountDownLatch and Condition do it for you, with fewer traps. But the producer-consumer with wait/notify is an interview favorite, and "why while, not if?" is the follow-up question every interviewer asks.

Key takeaways

  1. Call wait/notify only while holding that object's lock
  2. wait() releases the lock; it re-acquires it before returning
  3. Guard with while (!condition) wait(); — never if
  4. Prefer notifyAll() unless one waiter is surely enough
🤯 Did you know?

Spurious wakeups are officially allowed: Object.wait()'s own javadoc warns a thread can wake without notify, and recommends the while-loop idiom.

Practice questions

What does this print?

Object lock = new Object();
lock.notify();
System.out.println("notified");
  1. Throws IllegalMonitorStateException
  2. notified
  3. Compile error
  4. Throws InterruptedException
Check your answer

Throws IllegalMonitorStateException. notify() requires the calling thread to own the object's monitor. Without synchronized (lock), it throws IllegalMonitorStateException.

Why is notifyAll() usually safer than notify()?

  1. notify() wakes one arbitrary waiter, which may be waiting for a different condition
  2. notifyAll() releases the lock immediately
  3. notify() only works on the main thread
  4. notifyAll() removes the need for a while loop
Check your answer

notify() wakes one arbitrary waiter, which may be waiting for a different condition. If the single woken thread can't proceed, the signal is lost and others may wait forever. notifyAll() wakes everyone, and each re-checks its own condition.

Next: some threads are so unimportant that the JVM won't even wait for them to finish. Meet daemon threads.