skip to content

What do wait(), notify(), and notifyAll() do, and what precondition must hold whenever you call any of them?

level: middleimportance: must knowfreq 78%

answer

  1. Methods live on Object, not Thread
  2. Must hold the monitor → else IllegalMonitorStateException
  3. wait releases the lock and re-acquires on return
  4. Always wait in a while loop (spurious wakeups)
  5. notifyAll = safe default; notify = one arbitrary waiter

basics

~20 s

wait() makes the current thread pause and release the object's lock until another thread calls notify() or notifyAll() on the same object. notify() wakes one waiting thread; notifyAll() wakes them all. You must hold that object's lock (be inside a synchronized block on it) when you call any of the three, or you get IllegalMonitorStateException.

solid answer

~40 s

These three methods live on java.lang.Object and implement the monitor's wait-set: a place threads park until a condition becomes true. wait() atomically releases the object's intrinsic lock and suspends the calling thread, adding it to that object's wait-set; when later woken it re-acquires the lock before returning. notify() moves one arbitrary thread from the wait-set to runnable; notifyAll() moves all of them. All three require the caller to already hold the object's monitor (be inside a synchronized block/method on that same object), otherwise the JVM throws IllegalMonitorStateException. Because a thread can wake without anyone signaling (spurious wakeup) and because the condition may have changed between notify and re-acquisition, wait() must always be called inside a while loop that re-checks the guard condition, never an if.

go deeper

for a junior

Knows wait() pauses a thread and notify() wakes it, and that they're used for thread coordination. May not recall the lock-ownership rule or the loop requirement.

for a middle

States all three are Object methods requiring monitor ownership, that wait() releases the lock, and that wait must sit in a while loop because of spurious wakeups.

for a senior

Explains the atomic release-and-park, re-acquisition on return, the lost-wakeup race the synchronized requirement prevents, and chooses notifyAll vs notify deliberately.

for a principal

Frames wait/notify as the primitive under higher-level java.util.concurrent tools, discusses fairness/thundering-herd trade-offs, and steers teams toward BlockingQueue or Lock/Condition for maintainability.

## The problem these solve Sometimes a thread cannot make progress until some *condition* becomes true — e.g. a consumer cannot take from an empty queue until a producer adds an item. Busy-waiting (looping and re-checking while holding CPU) wastes cycles. Java provides a way for a thread to **block until notified**, releasing the CPU in the meantime. This is the `wait`/`notify`/`notifyAll` mechanism. ## Key terms, defined from scratch - **Monitor / intrinsic lock:** every Java object has an associated lock (a 'monitor'). Entering a `synchronized` block on an object acquires that object's monitor; leaving releases it. Only one thread can hold a given monitor at a time. - **Wait-set:** each monitor also has an associated *wait-set* — a holding area of threads that have called `wait()` on that object and are parked, not competing for the lock. - **These three methods are on `java.lang.Object`**, not Thread, because the lock and wait-set belong to *objects*. Any object can be used as a condition variable. ## What each call does - **`obj.wait()`**: (1) verifies the current thread holds `obj`'s monitor; (2) **atomically releases** that monitor and suspends the thread, placing it in `obj`'s wait-set. When the thread is later woken, (3) it must **re-acquire** `obj`'s monitor before `wait()` returns — so on return it again holds the lock. There are overloads `wait(long timeoutMillis)` and `wait(long, int nanos)` that also wake after the timeout elapses. - **`obj.notify()`**: moves **one** arbitrary thread from `obj`'s wait-set to the runnable state (it still has to re-acquire the lock before proceeding). Which one is unspecified. - **`obj.notifyAll()`**: moves **all** threads in `obj`'s wait-set to runnable; they then contend for the lock one at a time. ## The mandatory precondition You must **own `obj`'s monitor** when calling any of the three — i.e. be inside `synchronized(obj){ ... }` (or a synchronized instance method calling `this.wait()`, or a synchronized static method using the Class object). If you call them without holding that exact monitor, the JVM throws `IllegalMonitorStateException` at runtime. This rule exists because the check-condition-then-wait sequence must be atomic: you check the guard *while holding the lock*, and `wait()` releases it atomically so no signal can slip in between the check and the park (the 'lost wakeup' it prevents). ## Spurious wakeups and the guarded-loop rule A thread in `wait()` may return **without** any `notify` having occurred — a *spurious wakeup*, permitted by the JVM/OS for implementation reasons. Also, by the time a notified thread re-acquires the lock, another thread may have already consumed the condition. Therefore the condition must be **re-checked in a loop**: ```java synchronized (lock) { while (!conditionHolds()) { // while, never if lock.wait(); } // condition is true AND we hold the lock } ``` Using `if` here is the classic bug: a spurious wakeup or a stale notify lets the thread proceed with the condition false. ## notify vs notifyAll `notify()` wakes one waiter and is cheaper, but is only safe when **all** waiters are interchangeable and waiting on the **same** condition — otherwise you can wake the 'wrong' thread (one whose condition is still false), which goes back to sleep while the thread that *could* proceed is never woken (a stuck system). `notifyAll()` is the safe default: every waiter wakes, re-checks its guard, and only the eligible ones proceed; the rest loop back to `wait()`. The cost is a 'thundering herd' of wakeups contending for the lock. ## Why it's largely legacy From `java.util.concurrent` you usually prefer higher-level tools: `BlockingQueue` for producer-consumer, `CountDownLatch`/`CyclicBarrier`/`Semaphore` for coordination, or `Lock` + `Condition` (`await`/`signal`/`signalAll`) which gives multiple named wait-sets per lock. But `wait`/`notify` remains the foundation interviewers probe.

  • Why are these methods on Object rather than Thread?
    Because the lock and wait-set are properties of the object being used as the monitor/condition variable, not of a thread. Any object can serve as a condition, so each object carries its own wait-set.
  • What exactly throws IllegalMonitorStateException?
    Calling wait(), notify(), or notifyAll() on an object whose monitor the current thread does not hold — i.e. outside a synchronized block/method on that same object.

saying these in an interview costs you the question

  • Saying wait/notify are Thread methods
  • Calling wait() inside an if instead of a while
  • Believing wait() keeps holding the lock while parked (it releases it)
  • Claiming notify() lets you choose which thread wakes
  • Calling wait/notify outside any synchronized block and expecting it to work

context