Thread Synchronization Basics
wait() and notify() are instance methods of class Object, so any object can coordinate two threads
A Queue That Makes Consumers Wait
Imagine you want to build your own blocking queue. In a blocking queue, a consumer that asks for an item does not fail when the queue is empty. Instead, it simply waits until an item arrives. To build one, you need a way to hold a thread in a waiting state until some event happens. That event is usually caused by a different thread, such as a producer adding an item to the queue. So the problem involves at least two threads: one that waits, and one whose action wakes it up.
This article explains the basic mechanism Java provides for that handoff: wait() and notify(). Both are instance methods of class Object, which means any object can be used to coordinate two threads. The rest of the article traces how that handoff works step by step, and then looks at the case where things go wrong.
What do you think happens?
Suppose one thread calls notify() on an object, and only afterwards does a second thread call wait() on that same object. Does the earlier notify() wake the second thread?
Reveal answer
No. Calling notify() when no thread is waiting is not an error and has no effect, so the later wait() still blocks.
A notify() that arrives while nobody is waiting does not leave a stored wake-up for a later wait(). The waiting thread will sleep until some other notify() happens on that object. Keep this in mind, because it is the source of the most common handoff bug covered later in the article.
Why One Thread Has to Pause
The blocking queue is the most visible example, but it is not the only one. Any time one thread produces a result that another thread needs, the order of their work is constrained. The consuming thread cannot finish its computation until the result exists. It could repeatedly check whether the result is ready, but the source describes a cleaner approach: let the consuming thread stop and wait, and then wake it up when the result is ready.
Wait and notify are the mechanism Java provides for this kind of handoff. The waiting thread is not busy-checking. It pauses, and another thread's action resumes it.
Thread A in this pattern does not always wait. It waits only when its check finds that the result is not yet available. If the result already exists, the if condition is false, the call to wait() is skipped, and Thread A moves straight on to using the result. The pause is therefore conditional: it happens only when there is something to wait for.
Tracing a Wait and Notify Handoff
The clearest way to understand the handoff is to follow the calls in time order. Two threads are involved. Thread A is the consumer that needs a result. Thread B is the thread that produces it. Both work on the same shared object, and wait() and notify() are called on that object.
- 1Thread A checks whether the result is available. It is not, so the if condition is true.
- 2Thread A calls
wait()on the shared object. It stops running and enters the waiting state. - 3Thread B produces the result, then calls
notify()on the same shared object. - 4Thread A is woken. It resumes running at the line directly after its
wait()call and uses the result.
The diagram reads as the life of one thread. The key detail is that the notification must target the same object that the waiting thread called wait() on. Thread A stays in the waiting state until notify() is called on that object, and it resumes only then.
How a Thread Moves Between States
A thread that calls wait() on an object sleeps until another thread calls notify() on that same object. So from the waiting thread's point of view, there are only two situations to track: it is running, or it is waiting. The transition from running to waiting is caused by reaching wait(). The transition back to running is caused by a notify() on the same object.
Notice that the thread does not move from waiting to running on its own. Something has to call notify() on the object it is waiting on. This is why the producing thread's action is the one that wakes the consumer.
When notify() Arrives Too Early
The handoff only works when the notify() happens while the waiting thread is actually waiting. Calling notify() when no thread is waiting is not an error and has no effect. The signal does not wait around for a future wait() call. This is the failure mode that causes the handoff to go wrong.
A notify that arrives before the consumer waits
Thread B produces a result and calls notify() on a shared object. Only afterwards does Thread A check the result, find it missing, and call wait() on the same object. Does Thread A wake up?
- 1Identify the notify
Thread B calls
notify()on the shared object while no thread is waiting on it. - 2Apply the rule
Calling
notify()when no thread is waiting is not an error and has no effect. The signal is not kept for later. - 3Reach the wait
Thread A later checks, finds the result not available, and calls
wait()on the same object. - 4Check for a wake-up
No
notify()is pending for Thread A, because the earlier one had no effect. Thread A enters the waiting state and sleeps. - 5Locate the failure
The handoff went wrong at the moment Thread B called
notify(). The result exists, but the consumer never receives the wake-up and stays asleep.
Thread A stays blocked. The earlier notify() was lost, so the later wait() blocks anyway.
Mistakes That Break a Handoff
Common mistake Assuming notify() is remembered for a later wait()
What it looks likeThread B calls notify() first, and Thread A calls wait() afterwards and expects to continue immediately.
Why it failsCalling
notify()when no thread is waiting has no effect, so there is no stored wake-up for the laterwait().Do this insteadMake sure the waiting thread reaches wait() before the notifying thread sends its notify(), or design the check so the consumer does not wait when the result already exists.
Common mistake Expecting an error when notify() has no waiter
What it looks likeA developer looks for an exception after calling notify() and sees nothing, then assumes the notification was delivered.
Why it failsThe source states that calling
notify()with no waiting thread is not an error. The silence is expected behavior, not confirmation of a successful handoff.Do this insteadDo not use the absence of an error as proof that the waiting thread was woken. Reason about the thread states instead.
Common mistake Notifying a different object than the one being waited on
Why it failswait()andnotify()are coordinated through the same object. A thread waiting on one object sleeps until anotify()on that same object.Do this insteadUse one shared object for both the wait() call and the notify() call.
Common mistake Waiting unconditionally, even when the result is already available
Choosing Higher-Level Tools
Wait and notify are the building blocks behind patterns such as blocking queues. The source notes that these methods can be used internally to implement blocking queues, and that Java has other ways of solving the wait and notify problem. The reader does not need to know whether Java's standard blocking queue classes use these methods internally.
Knowing the wait and notify pattern is still useful even when you choose a higher-level tool. It gives you a way to reason about how any blocking structure keeps threads in step. The source advises preferring higher-level tools when they fit the problem.
Practice: Trace a Handoff
A consumer thread, Thread A, finds a shared result missing and calls wait() on a shared object. Later, a producer, Thread B, makes the result ready and calls notify() on the same object. Write out the order of the calls, and state at which point Thread A resumes running. Then change the scenario so that Thread B calls notify() before Thread A calls wait(). Describe what happens to Thread A and why.
Hints
- Start by listing which thread calls
wait()and which callsnotify(), and in what order. - Ask whether Thread A is in the waiting state at the moment
notify()is called. - Recall whether a
notify()with no waiting thread is kept for a laterwait().
Key Takeaways
wait()andnotify()are instance methods of class Object, so any object can coordinate two threads: a thread that callswait()on an object sleeps until another thread callsnotify()on that same object.- Waiting is useful whenever one thread needs a result or event from another thread, because the waiting thread can pause instead of repeatedly checking.
- The wait is conditional: a thread calls
wait()only when its check finds the result is not yet available, and otherwise continues without pausing. - Calling
notify()when no thread is waiting is not an error and has no effect, so anotify()that arrives beforewait()is lost and the laterwait()still blocks. - Wait and notify underlie patterns such as blocking queues, but higher-level tools are often preferable when they fit the problem.