Monitors, wait(), notifyAll()

Sometimes a thread cannot perform its job until an object reaches a certain state. In this situation, the programmer needs the capability for objects to block threads when conditions are not right, and then notify those threads when conditions have changed. What is needed is a "monitor" construct. In Java, any object with one or more synchronized methods is a monitor.

The wait() method introduced previously is the function to use to initiate the blocking of a thread; and the notifyAll() method is what is needed to broadcast to all blocked threads that conditions may now be ripe to continue processing. Both wait() and notifyAll() must be called in "synchronized code".

The last sentence is important. Do not let intuition lead you to put the wait() call inside the thread class. Instead, thread objects call synchronized methods on "monitor" objects, and it is the monitor that knows (and controls) whether the client thread should "wait". The monitor also knows when conditions have changed and waiting threads should "poke their head up and take another peek at the world around them".

There is a standard pattern that is important to use with wait(). The monitor class should have a method like the following.

   public synchronized void doWhenConditionIsTrue() {
      while ( ! condition)
         try { wait(); } catch( InterruptedException e ) { }
      // do what must be done when the condition is true
The condition test should always be in a loop. Never assume that being awakened means that the condition has been satisfied. In other words, don't change the while to an if. [Gosling, p188]

On the other side, the standard pattern for notifyAll() is as follows.

   public synchronized void changeCondition() {
      // change some value used in a condition test
The function notify() picks a single waiting thread and wakes it up. notifyAll() wakes up all waiting threads. You should almost always use notifyAll() instead of notify(). If you use the latter, you cannot dictate (or even determine) which thread will be chosen. The thread selected by the scheduler may be waiting on the monitor object for a condition that is different than the one just satisfied. That thread will discover that its condition has not been satisfied and go back to waiting, while the thread waiting on the condition you did satisfy will never get awakened. [Gosling, p189]

The example is architected to ensure that a withdrawal never happens when there are insufficient funds to cover it. While the Account balance is inadequate to satisfy the withdrawal request, the requesting thread is required to wait() (lines 10 and 12). Whenever a deposit comes in, the Account increases its balance by the designated amount, and then it notifies all waiting threads (line 7) so that they can examine whether conditions are sufficient to accomodate their withdrawal.

Two "withdrawal" threads (lines 30 and 33) and three "deposit" threads (lines 31, 34, and 36) are created and activated. The Account balance is insufficient to cover either withdrawal request until the second deposit is completed. When notifyAll() is called, the $20 withdrawal happened to be chosen first by the scheduler, and it now runs to completion (line 43). Next, the $40 withdrawal is woken up, and it retests the financial waters. Finding that funds are still insufficient, it goes back to waiting (line 44). After the final deposit is made, the $40 withdrawal finally succeeds.

Line 32 was added because line 33 was being granted the CPU before the $20 withdrawal thread had had a chance to wake up and report that it was still waiting (line 40). Line 35 was added because line 36 was being granted the CPU before the $40 withdrawal thread had had a chance to wake up and report that it was still waiting (line 44).