When is it safe to use notify() instead of notifyAll(), and what bug appears if you pick wrong?
answer
- notify = one arbitrary waiter; notifyAll = everyone
- notify safe only if all waiters share one interchangeable condition
- Wrong notify → lost wakeup / missed signal stall
- notifyAll = safe default, costs thundering herd
- Distinct conditions → use Lock + multiple Conditions
basics
~20 snotify() wakes one waiting thread; notifyAll() wakes all of them. notify() is only safe when every waiting thread is waiting for the exact same condition and any of them can proceed interchangeably. If different threads wait for different conditions, notify() might wake the 'wrong' one, which goes back to sleep, leaving the thread that could actually run never woken — the program stalls.
solid answer
~50 sBoth wake threads from the monitor's wait-set; notify() picks one arbitrary waiter, notifyAll() wakes them all. notify() is the cheaper option but is only correct under two conditions: all waiters wait on the same logical condition, and they are interchangeable (it doesn't matter which one proceeds). When those hold — e.g. a pool of identical worker waiters — notify() avoids the thundering-herd contention of notifyAll(). The classic failure is a 'missed signal' or 'lost wakeup' deadlock: producers and consumers share one monitor but wait on different conditions; a notify() after a put might wake another producer instead of a consumer. That producer re-checks its guard, finds it false, and goes back to wait(), while the consumer that should have run stays parked. notifyAll() is the safe default precisely because every waiter wakes, re-checks its own guard in its while loop, and only the eligible ones proceed.
go deeper
Knows notifyAll wakes all and notify wakes one; may not know when each is safe.
Can state that notifyAll is the safe default and notify risks waking the wrong thread, with a basic example.
Explains the interchangeability precondition for notify, describes the lost-wakeup stall concretely, and knows Lock/Condition as the proper fix.
Weighs thundering-herd cost vs lost-wakeup risk at scale, mandates multiple Conditions for distinct predicates, and reasons about fairness and latency implications in the design.
## The two methods A monitor's **wait-set** holds all threads that have called `wait()` on that object. - **`notify()`** transitions **one** thread from the wait-set to runnable. *Which* thread is unspecified — you cannot choose. The woken thread still has to re-acquire the monitor before returning from `wait()`. - **`notifyAll()`** transitions **all** waiting threads to runnable; they then contend for the monitor one at a time, each re-checking its guard. ## When notify() is safe `notify()` is correct only when **both** are true: 1. **One condition.** Every thread in the wait-set is blocked on the *same* predicate (e.g. 'an item is available'). 2. **Interchangeability.** It does not matter which of them runs — any one can consume the event that was just made true. A pool of identical consumers all waiting for 'queue non-empty', with each `put` adding exactly one item and waking exactly one consumer, fits this. Here `notify()` is both correct and more efficient because it avoids waking threads that will only go back to sleep. ## The bug when you pick wrong: lost-wakeup / missed-signal stall Consider a single monitor shared by **producers** (wait on 'not full') and **consumers** (wait on 'not empty'). Suppose the buffer was full, several producers are parked, and one consumer is also parked because of timing. A consumer removes an item and calls `notify()`. The runtime may wake **another consumer** rather than a producer. That consumer re-checks 'not empty', finds the buffer now empty (the item was taken), and returns to `wait()`. Meanwhile the **producers** — the threads that *could* now proceed because a slot opened — were never woken. The result: threads are blocked even though work is possible. This is a *lost wakeup* (also called missed signal) and it manifests as a hung program, often intermittently, making it nasty to debug. ## Why notifyAll() avoids it `notifyAll()` wakes **everyone**. Each thread re-evaluates its own guard in its `while` loop: the ones whose condition is now true proceed (one at a time, as they grab the lock), and the ones whose condition is still false simply loop back to `wait()`. No eligible thread is ever left asleep. The cost is the **thundering herd** — all waiters wake and contend for the lock, most only to sleep again — which wastes CPU under high contention but is *correct*. ## The better fix The real solution when you have distinct conditions is **not** to share one wait-set. Use a `ReentrantLock` with **multiple `Condition` objects** — one per logical condition (`notFull`, `notEmpty`). Then `notFull.signal()` / `notEmpty.signal()` wakes a thread from the *correct* wait-set, giving you `notify()`-level efficiency with correctness. Intrinsic monitors only have one wait-set per object, which is exactly why `notify()` is dangerous there. ## Rule of thumb Default to `notifyAll()`. Reach for `notify()` only after proving all waiters share one interchangeable condition — and prefer `Lock`+`Condition` when conditions differ.
- How do multiple Condition objects on a ReentrantLock solve the notify() problem cleanly?Each Condition is a separate wait-set. You await on the condition that matches your predicate and signal only that condition, so the right group of threads is woken — giving notify-level efficiency without lost wakeups.
- What is the downside of always using notifyAll()?Thundering herd: every waiter wakes and contends for the lock, but most re-check their guard, find it false, and go straight back to sleep, wasting CPU and scheduling under heavy contention.
saying these in an interview costs you the question
- Claiming notify() lets you choose which thread wakes
- Using notify() when producers and consumers share one monitor
- Thinking notifyAll() is always wrong because it's 'slower'
- Believing a woken thread proceeds immediately without re-acquiring the lock
- Not recognizing that one object has only one wait-set