Witscale Test Center

12.4 Thread interaction with wait(), notify() and notifyAll() > 12.4.3 Simple example using the wait(), notify() methods


12.4.3 Simple example using the wait(), notify() methods

Before we look into the wait-and-notify in complex stock application, let us see how to implement it in a simple example. Imagine that we have two threads, main thread and greeter thread. The main thread wants to wait till the  greeter thread prints the message “Good Morning” five times. Listing 12.7 has two clases MainThread and GreetingsThread.


 

When the MainThread class is executing, two threads are created, the main thread (created and started when main() method is called) and the greeter thread. The main thread starts greeter thread with greeter.start(). If we remove the synchronized block in main(), the main method may continue its own execution and print “Welcome Guest” and exit. But we do not want it to print the welcome message before the greeter thread prints the greetings. Therefore to prevent main for continuing after it starts the greeter thread, greeter.wait() method is invoked.  Note that this method is invoked in a synchronized code which synchronizes itself with the object greeter. We must do this  because in order to call wait() on the greeter, the main thread must first own a lock on greeter. Remember that a thread can call wait() or notify() on an object only when the thread owns the lock for that object.

Now while main is waiting, the greeter may get a chance to execute. It will the then print the greetings “Good Morning!” five times.  After that it notifies the thread which is waiting on greeter (in our case, it is the main thread) with a call to notify. Note that the call to notify()is also in a synchronized code. Since the main thread is waiting on greeter object, the notify method is also called on the greeter object. But the thread executing the call notify() itself is the greeter object, therefore it calls this.notify() (or simply notify() ). For the same reason, the call to notify()is in synchronized(this) {} block.

After receiving the notification, the main thread will continue from where it left off and prints the welcome message. Therefore the output would be as following.

 

Waiting for greeter to complete...

Good Morning!

Good Morning!

Good Morning!

Good Morning!

Good Morning!

Welcome Guest

 

Thus the output of listing 12.7 will always have “Welcome Guest” printed after “Good Morning!” is printed five times.  Thus the two threads can now successfully communicate with the wait-and-notify mechanism about the state of the shared object stock in which they both have interests. This example gives you ideas how the wait() and notify() are used. Let us see now what happens behind the scene when these methods are invoked.

What exactly happens when wait() is called?


When main thread enters the synchronized block, it acquires the lock on greeter object. Interestingly, when the main thread starts waiting (by invoking greeter.wait() ) , it temporarily releases the lock for other threads to use.  Figure 12.14 illustrates how the main thread releases the lock on greeter object immediately after the greeter.wait() is invoked.

 

 

Figure 12.14 Detailed steps the thread performs before and just after the wait() method is called.

 

Thus main thread will not execute any statement after wait() till it receives a notification. After receiving notification the main thread continues its execution from the next line after the call to greeter.wait(). Now since it is still in the synchronized code, it must have the lock on greeter. But we just saw that it release the lock while waiting for notification. Therefore the main thread needs to reacquire the lock before it can re-assume its execution. Figure 12.15 illustrates what happens when the waiting main thread is notified.

 


Figure 12.15 Detailed steps just after the the main thread is notified

 

After receiving the notification, the main thread comes out of the waiting list. It cannot immediately start running however. It has to pass two hurdles. It has to re-acquire the lock on greeter object and then it has to content with other threads to get its turn of execution. Note that after the synchronized block is executed completely, the lock on greeter is once again released.

 

@

Please note that if the thread is holding a lock and went to a sleeping state, it does not loose the lock. However, when thread is waiting for notification (by a call to wait()), it releases the lock it is holding. This eliminates the potential of risk of deadlocking waiting and notifying in wait-and-notify because the thread responsible for notification can acquire the same object’s lock without any problem.

 

What exactly happens when notify() is called?

The greeter thread calls the notify to let the waiting main thread know that it has finished doing what main was waiting for ( that is it has printed the greetings five times). The important thing to remember about notify() is that it does not immediately release the lock of the object on which it is called. Figure 12.16 illustrates how the greeter thread release the lock (on itself) only after it executes the synchronized block completely.


Figure 12.16 Detailed steps when the greeter thread notifies with notify() method

 

Therefore even if the main thread is notified by greeter thread, it cannot start executing till the greeter thread is executing its synchronized block.

 

@

When the wait()method is invoked on an object, the thread executing that code gives up its lock on the object immediately. However, when notify()is called, the thread does not necessarily give up its lock immediately. If the thread is still completing synchronized code, the lock is not released until the thread moves out of synchronized code.

 

A call to wait() and notify() can throw at IllegalMonitorStateException runtime

In our example, we have called the wait() and notify methods in appropriate synchronized code. But what if a thread calls these methods on a object without first acquiring the lock for that object? For example, if the main thread calls the wait() as:

 

  public static void main(String [] args) {

    GreetingsThread greeter = new GreetingsThread();

    greeter.start();

      try {

        System.out.println("Waiting for greeter to complete...");

greeter.wait();                                                    

      } catch (InterruptedException e) {}                                            

   System.out.println("Welcome Guest");

  }

 

Note that the wait() is not enclosed in a synchronized block. Now your code compiles even if main thread does not own the greeter object’s lock. But you will get a runtime exception IllegalMonitorStateException saying “java.lang.IllegalMonitorStateException: current thread not owner” . You do not get a compile-time error because this exception is not a checked exception.

 

@

Whenever you see a question with calls on wait() and notify(),always make sure whether the thread executing it has acquired the lock for the same object on which wait() or notify() is called. The thread can do so either using a synchronized block or with a synchronized method.

 

The wait() method throws a checked exeception called InterruptedException

Note that the call to wait() method in listing 12.7 is surrounded by try-catch block.  This method throws the InterruptedException. Since it is a checked exception, you must take care of this exception (either handle it with try-catch block or declare it with throws clause in method):

Overloaded versions of wait()

The wait() method without any argument causes the thread to wait (indefinitely) until it is notified. There are two more versions of wait() which gives you a flexible options for a timed waiting. Method signatures of these overloaded version are :

 

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

 

 The first version accepts a long value as a timeout. This value denotes the maximum number of milliseconds you (the thread) would like wait. Unless interrupted,  the thread calling wait(long) continues to wait until –

 

It is notified Or

The specified timeout (in milliseconds) has elapsed.

 

In  the second version of wait, you can specify the timeout to nanoseconds precision. For example, wait(3000,3) means the thread will wait for 3000 milliseconds and 3 nanoseconds. When a thread gets out of the waiting (for notification) state, it still needs to reacquire the lock for the object. So we cannot say for sure that a thread that calls wait(3000) will resume execution after at most 3000 milliseconds. All we can say is that it will wait for notification for at most 3000 milliseconds.