Õ¾ÄÚËÑË÷: ÇëÊäÈëËÑË÷¹Ø¼ü´Ê
µ±Ç°Ò³Ãæ: ͼÊéÊ×Ò³ > Java Threads, Third Edition

4.1 Wait and Notify - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

4.1 Wait and Notify

We've seen that every Java object has a lock. In addition, every object also provides a mechanism that allows it to be a waiting area; this mechanism aids communication between threads.[1] The idea behind the mechanism is simple: one thread needs a certain condition to exist and assumes that another thread will create that condition. When another thread creates the condition, it notifies the first thread that has been waiting for the condition. This is accomplished with the following methods of the Object class:

[1] With Solaris or POSIX threads, these are often referred to as condition variables; with Windows, they are referred to as event variables.


void wait()

Waits for a condition to occur. This method must be called from within a synchronized method or block.


void wait(long timeout)

Waits for a condition to occur. However, if the notification has not occurred in timeout milliseconds, it returns anyway. This method must be called from a synchronized method or block.


void wait(long timeout, int nanos)

Waits for a condition to occur. However, if the notification has not occurred in timeout milliseconds and nanos nanoseconds, it returns anyway. This method must be called from a synchronized method or block. Note that, just like the sleep() method, implementations of this method do not actually support nanosecond resolution.


void notify()

Notifies a thread that is waiting that the condition has occurred. This method must be called from within a synchronized method or block.

wait( ), notify( ), and the Object Class

Just like the synchronized method, the wait-and-notify mechanism is available from every object in the Java system. However, this mechanism is accomplished by method invocations whereas the synchronized mechanism is handled by adding a keyword.

The wait() and notify() mechanism works because these are methods of the Object class. Since all objects in the Java system inherit directly or indirectly from the Object class, all objects are also instances of the Object class and therefore have support for this mechanism.


What is the purpose of the wait-and-notify mechanism, and how does it work? The wait-and-notify mechanism is a synchronization mechanism. However, it is more of a communication mechanism: it allows one thread to communicate to another thread that a particular condition has occurred. The wait-and-notify mechanism does not specify what the specific condition is.

Can the wait-and-notify mechanism be used to replace the synchronized mechanism? Actually, the answer is no; wait-and-notify does not solve the race condition problem that the synchronized mechanism solves. As a matter of fact, wait-and-notify must be used in conjunction with the synchronized lock to prevent a race condition in the wait-and-notify mechanism itself.

Let's use this technique to solve the efficiency problem in our animation component. In this fixed version, the animation thread does not exit when the done flag is set. Instead, it simply waits for the done flag to be reset.

package javathreads.examples.ch04.example1;

...

public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas

                    implements CharacterListener, Runnable {

    private boolean done = true;

    ...

    public synchronized void run( ) {

        while (true) {

            try {

                if (done) {

                    wait( );

                } else {

                    repaint( );

                    wait(100);

                }

            } catch (InterruptedException ie) {

                return;

            }

        }

    }



    public synchronized void setDone(boolean b) {

        done = b;



        if (timer == null) {

            timer = new Thread(this);

            timer.start( );

        }

        if (!done)

            notify( );

    }

 }

In this new version, the done flag is no longer volatile. This is because we are doing more than just setting the flag; we also need to send a notification atomically while setting the flag. Therefore, access to the done flag is now protected by a synchronized lock.

The run() method now no longer exits when the done flag is set to false. Instead, it calls the wait() method (with no arguments). The thread waits (or blocks) in that method until another thread calls the notify method, at which point it restarts the animation.

Also notice that instead of calling the sleep() method, the animation is achieved by calling the wait( ) method with a 100 millisecond timeout. This is due to the differences between the wait() and sleep() methods. Unlike the sleep( ) method, the wait() method requires that the thread own the synchronization lock of the object. When the wait() method executes, the synchronization lock is released (internally by the virtual machine itself). Upon receiving the notification, the thread needs to reacquire the synchronization lock before returning from the wait() method.

This technique is needed due to a race condition that would otherwise exist between setting and sending the notification and testing and getting the notification. If the wait() and notify() mechanism were not invoked while holding the synchronization lock, there would be no way to guarantee that the notification would be received. And if the wait() method did not release the lock prior to waiting, it would be impossible for the notify() method to be called (as it would be unable to obtain the lock). This is also why we had to use the wait() method instead of the sleep() method; if the sleep( ) method were used, the lock would never be released, the setDone() method would never run, and notification could never be sent.

In the online examples, the random character generator's restarting issue has also been fixed. We'll leave it up to you to examine the code at your leisure.

4.1.1 The Wait-and-Notify Mechanism and Synchronization

As we just mentioned, the wait-and-notify mechanism has a race condition that needs to be solved with the synchronization lock. It is not possible to solve the race condition without integrating the lock into the wait-and-notify mechanism. This is why it is mandatory for the wait() and notify() methods to hold the locks for the object on which they are operating.

The wait() method releases the lock prior to waiting and reacquires the lock prior to returning from the wait() method. This is done so that no race condition exists. As you recall, there is no concept of releasing and reacquiring a lock in the Java API. The wait() method is actually tightly integrated with the synchronization lock, using a feature not available directly from the synchronization mechanism. In other words, it is not possible for us to implement the wait() method purely in Java: it is a native method.

This integration of the wait-and-notify mechanism and the synchronization lock is typical. In other systems, such as Solaris or POSIX threads, condition variables also require that a mutex lock be held for the mechanism to work.

In our example, both the run() and the setDone() methods are synchronized. In the previous chapter, this was not a recommended technique since the run() method never completes — in fact, some of our examples showed how the application broke as a result of synchronizing the run() method. However, because of the way the wait() method works, there is no longer a danger of deadlock in the example we've just shown. The wait() method releases the lock, which allows other threads to execute, including the thread that eventually executes the setDone() method. Before the wait() method returns, it reacquires the lock. To the developer, it appears as if the lock has been held the entire time.

What happens when notify() is called and no thread is waiting? This cannot happen in our animation component. Since the run() method does not exit, it is not possible for the lock to be freed without the thread being in a wait() method call. However, in general this is not the case: it is not required that some thread be executing the wait() method when another thread calls the notify() method. Since the wait-and-notify mechanism does not know the condition about which it is sending notification, it assumes that a notification goes unheard if no thread is waiting. In other words, if the notify() method is called when no other thread is waiting, notify() simply returns and the notification is lost. A thread that later executes the wait() method has to wait for another notification to occur.

What are the details of the race condition that exists in the wait-and-notify mechanism? In general, a thread that uses the wait() method confirms that a condition does not exist (typically by checking a variable) and then calls the wait() method. When another thread establishes the condition (typically by setting the same variable), it calls the notify() method. A race condition occurs when:

  1. The first thread tests the condition and confirms that it must wait.

  2. The second thread sets the condition.

  3. The second thread calls the notify() method; this goes unheard since the first thread is not yet waiting.

  4. The first thread calls the wait() method.

How does this potential race condition get resolved? This race condition is resolved by the synchronization lock discussed earlier. In order to call the wait() or notify() methods, we must have obtained the lock for the object on which we're calling the method. This is mandatory; the methods do not work properly and generate an exception condition if the lock is not held. Furthermore, the wait() method also releases the lock prior to waiting and reacquires the lock prior to returning from the wait() method. The developer must use this lock to ensure that checking the condition and setting the condition is atomic, which typically means that the check or set must be within the lock scope.

Is there a race condition during the period that the wait() method releases and reacquires the lock? The wait() method is tightly integrated with the lock mechanism. The object lock is not actually freed until the waiting thread is already in a state in which it can receive notifications. This would have been difficult, if not impossible, to accomplish if we had needed to implement the wait() and notify() methods ourselves. The system prevents any race conditions from occurring in this mechanism.

If a thread receives a notification, is it guaranteed that the condition is set correctly? Simply, no. Prior to calling the wait() method, a thread should always test the condition while holding the synchronization lock. Upon returning from the wait() method, the thread should always retest the condition to determine if it should wait again. This is because another thread can also test the condition and determine that a wait is not necessary — processing the valid data that was set by the notification thread.

Let's look into how that can happen. Our animated canvas example is very simple; only one thread is actually waiting. In most programs, many threads are waiting and sending notifications. A race condition exists when multiple threads are waiting for notification. The race condition that is solved internally to the wait-and-notify mechanism prevents the loss of notifications, but it does not solve the following scenario when multiple threads are waiting:

  1. Thread 1 calls a method that acquires the synchronization lock.

  2. Thread 1 examines a state flag and determines that the data is not in the desired state.

  3. Thread 1 calls the wait() method, which frees the lock.

  4. Thread 2 calls a method that acquires the same synchronization lock.

  5. Thread 3 calls a method that blocks waiting for the lock.

  6. Thread 2 sets the state flag and calls the notify() method.

  7. Thread 2 finishes its method and frees the lock.

  8. Thread 3 acquires the lock and proceeds to process the data; it sees that the data is in the desired state, so it processes the data and resets the state flag.

  9. Thread 3 exits without needing to wait.

  10. Thread 1 receives the notification and wakes up.

This is a common case when multiple threads are involved in the notifications. More particularly, the threads that are processing the data can be thought of as consumers; they consume the data produced by other threads. There is no guarantee that when a consumer receives a notification that it has not been processed by another consumer. As such, when a consumer wakes up, it cannot assume that the state it was waiting for is still valid. It may have been valid in the past, but the state may have been changed after the notify() method was called and before the consumer thread woke up. Waiting threads must provide the option to check the state and to return back to a waiting state in case the notification has already been handled. This is why we always put calls to the wait() method in a loop.

Remember too that the wait() method can return early if its thread is interrupted. In that case, processing is application-specific, depending on how the algorithm needs to handle the interruption.

4.1.2 wait( ), notify( ), and notifyAll( )

What happens when more than one thread is waiting for notification? Which threads actually get the notification when the notify() method is called? It depends: the Java specification doesn't define which thread gets notified. Which thread actually receives the notification varies based on several factors, including the implementation of the Java virtual machine and scheduling and timing issues during the execution of the program. There is no way to determine, even on a single processor platform, which of multiple threads receives the notification.

Another method of the Object class assists us when multiple threads are waiting for a condition:


void notifyAll()

Notifies all the threads waiting on the object that the condition has occurred. This method must be called from within a synchronized method or block.

The notifyAll() method is similar to the notify() method except that all of the threads that are waiting on the object are notified instead of a single arbitrary thread. Just like the notify() method, the notifyAll() method does not allow us to decide which thread gets the notification: they all get notified. When all the threads receive the notification, it is possible to work out a mechanism for the threads to choose among themselves which thread should continue and which thread(s) should call the wait() method again.

Does the notifyAll() method really wake up all the threads? Yes and no. All of the waiting threads wake up, but they still have to reacquire the object lock. So the threads do not run in parallel: they must each wait for the object lock to be freed. Thus, only one thread can run at a time, and only after the thread that called the notifyAll() method releases its lock.

Why would you want to wake up all of the threads? There are a few reasons. For example, there might be more than one condition to wait for. Since we cannot control which thread gets the notification, it is entirely possible that a notification wakes up a thread that is waiting for an entirely different condition. By waking up all the threads, we can design the program so that the threads decide among themselves which thread should execute next.[2]

[2] Later in this chapter, we discuss options to allow multiple condition variables to coexist. This allows different threads to wait for different conditions efficiently.

Another option could be when producers generate data that can satisfy more than one consumer. Since it may be difficult to determine how many consumers can be satisfied with the notification, an option is to notify them all, allowing the consumers to sort it out among themselves.

4.1.3 Wait-and-Notify Mechanism with Synchronized Blocks

In our example, we showed how the wait() and notify() methods are called within a synchronized method. In that case, the lock that interacts with the wait() and notify() methods is the object lock of the this object.

It is possible to use the wait() and notify() methods with a synchronized block. In that case, the lock that the code holds is probably not the object lock of the code: it is most likely the lock of some object explicitly specified in the synchronized block. Therefore, you must invoke the wait() or notify() method on that same object, like this:

package javathreads.examples.ch04.example2;

...

public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas 

                   implements CharacterListener, Runnable {

    ...

    private Object doneLock = new Object( );



    public synchronized void newCharacter(CharacterEvent ce) {

        ...

    }



    protected synchronized void paintComponent(Graphics gc) {

        ...

    }



    public void run( ) {

        synchronized(doneLock) {

            while (true) {

                try {

                    if (done) {

                        doneLock.wait( );

                    } else {

                        repaint( );

                        doneLock.wait(100);

                    }

                } catch (InterruptedException ie) {

                    return;

                }

            }

        }

    }



    public void setDone(boolean b) {

        synchronized(doneLock) {

            done = b;



            if (timer == null) {

                timer = new Thread(this);

                timer.start( );

            }

            if (!done)

                doneLock.notify( );

        }

    }

 }

In this example, we've separated the synchronization that protects the animation (the tmpChar[] and curX variables) from the synchronization that protects the thread state (the timer and done variables). In programs with a lot of contention for object locks, this technique is useful since it allows more threads to access different methods at the same time (e.g., two threads can now simultaneously access the paintComponent() and run() methods).

Now when the wait() and notify() methods are called, we're holding the object lock of the doneLock object. Consequently, we explicitly call the doneLock.wait() and doneLock.notify() methods. That follows the same logic we outlined earlier; it's simply a different lock now.

It may help to remind yourself how Java objects work in this regard. In our first example, we had this statement:

wait( );

which is equivalent to this statement:

this.wait( );

So the wait() and notify() methods are consistent: they are always called with an object reference, even if that reference is the implied this object. The object reference must always be one that you hold the object lock for—and again, the synchronized method grabs the object lock of the this object.

    Previous Section  < Day Day Up >  Next Section