O'Reilly logo

Java Threads, 3rd Edition by Henry Wong, Scott Oaks

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Thread Notification

In the previous chapter, we discussed data synchronization. Using synchronization and explicit locks, threads can interoperate and safely share data without any race conditions that might corrupt the state of the data. However, as we shall see, synchronization is more than avoiding race conditions: it includes a thread-based notification system that we examine in this chapter.

Thread notification addresses a number of issues in our sample application. Two of these relate to the random character generator and the animation canvas. The random character generator is created when the user presses the Start button; it is destroyed when the user presses the Stop button. Therefore, the listeners to the random character generator are reconnected each time the Start button is pressed. In fact, the entire initialization process is repeated every time that the Start button is pressed.

A similar problem exists for the animation component. Although the component itself is not destroyed every time the user restarts, the thread object that is used for the animation is discarded and recreated. The component provides a mechanism that allows the developer to set the done flag, but the component doesn't use that data to restart the animation: once the done flag is set to true, the run() method of the animation canvas exits. The reason for this has to do with efficiency. The alternative is to loop forever, waiting for the done flag to be set to false. This consumes a lot of CPU cycles. Fortunately, the mechanisms we explore in this chapter can solve all these problems.

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:

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.

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.

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.

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]

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.

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.

Condition Variables

Condition variables are a type of synchronization provided by many other threading systems. A condition variable is very similar to Java's wait-and-notify mechanism—in fact, in most cases it is functionally identical. The four basic functions of a POSIX condition variable—wait(), timed_wait(), signal(), and broadcast( )—map directly to the methods provided by Java (wait(), wait(long), notify(), and notifyAll(), respectively). The implementations are also logically identical. The wait() operation of a condition variable requires that a mutex lock be held. It releases the lock while waiting and reacquires the lock prior to returning to the caller. The signal() function wakes up one thread whereas the broadcast() function wakes up all the waiting threads. These functions also require that the mutex be held during the call. The race conditions of a condition variable are solved in the same way as those of Java's wait-and-notify mechanism.

There is one subtle difference, however. The wait-and-notify mechanism is highly integrated with its associated lock. This makes the mechanism easier to use than its condition variable counterpart. Calling the wait() and notify() methods from synchronized sections of code is just a natural part of their use. Using condition variables, however, requires that you create a separate mutex lock, store that mutex, and eventually destroy the mutex when it is no longer necessary.

Unfortunately, that convenience comes at a small price. A POSIX condition variable and its associated mutex lock are separate synchronization entities. It is possible to use the same mutex with two different condition variables, or even to mix and match mutexes and condition variables in any scope. While the wait-and-notify mechanism is much easier to use and is usable for most cases of signal-based synchronization, it is not capable of assigning any synchronization lock to any notification object. When you need to signal two different notification objects while requiring the same synchronization lock to protect common data, a condition variable is more efficient.

J2SE 5.0 adds a class that provides the functionality of condition variables. This class is used in conjunction with the Lock interface. Since this new interface (and, therefore, object) is separate from the calling object and the lock object, its usage is just as flexible as the condition variables in other threading systems. In Java, condition variables are objects that implement the Condition interface. The Condition interface is tied to the Lock interface, just as the wait-and-notify mechanism is tied to the synchronization lock.

To create a Condition object from the Lock object, you call a method available on the Lock object:

Lock lockvar = new ReentrantLock( );
Condition condvar = lockvar.newCondition( );

Using the Condition object is similar to using the wait-and-notify mechanism, with the Condition object's await() and signal() method calls replacing the wait() and notify() methods. We'll modify our typing program to use the condition variable instead of the wait-and-notify methods. This time, we'll show the implementation of the random character generator; the code for the animation character class is similar and can be found online.

package javathreads.examples.ch04.example3;
...
public class RandomCharacterGenerator extends Thread implements CharacterSource {
    ...
    private Lock lock = new ReentrantLock( );
    private Condition cv = lock.newCondition( );
    ...
    public void run( ) {
        try {
            lock.lock( );
            while (true) {
                try {
                    if (done) {
                        cv.await( );
                    } else {
                        nextCharacter( );
                        cv.await(getPauseTime( ), TimeUnit.MILLISECONDS);
                    }
                } catch (InterruptedException ie) {
                    return;
                }
            }
        } finally {
            lock.unlock( );
        }
    }

    public void setDone(boolean b) {
        try {
            lock.lock( );
            done = b;

            if (!done) cv.signal( );
        } finally {
            lock.unlock( );
        }
    }
}

As we mentioned, a new Condition object is created by calling the newCondition() method provided by the Lock interface. This new Condition object is bound to the Lock instance whose method is called. This means that the lock of the Lock instance must be held in order to use the Condition object; it also means that the Condition object releases and reacquires the lock similar to the way Java's wait-and-notify mechanism works with synchronization locks.

Therefore, our new random character generator now uses a Lock object as its synchronization lock. We instantiate a Condition object, cv, which is set to the value returned by the newCondition() method of the lock object. Furthermore, calls to the wait() and notify() method are replaced by the condition object's await() and signal() method.

In this example, it doesn't look like we accomplished anything: all we do is use different methods to accomplish what we were previously able to accomplish using the wait-and-notify mechanism. In general, condition variables are necessary for several reasons.

First, condition variables are needed when you use Lock objects. Using the wait() and notify() methods of the Lock object will not work since these methods are already used internally to implement the Lock object. More importantly, just because you hold the Lock object doesn't mean you hold the synchronization lock of that object. In other words, the lock represented by the Lock object and the synchronization lock associated with the object are distinct. We need a condition variable mechanism that understands the locking mechanism provided by the Lock object. This condition variable mechanism is provided by the Condition object.

The second reason is the creation of the Condition object. Unlike the Java wait-and-notify mechanism, Condition objects are created as separate objects. It is possible to create more than one Condition object per lock object. That means we can target individual threads or groups of threads independently. With the standard Java mechanism, all waiting threads that are synchronizing on the same object are also waiting on the same condition.

Here are all the methods of the Condition interface. These methods must be called while holding the lock of the object to which the Condition object is tied:

void await()

Waits for a condition to occur.

void awaitUninterruptibly()

Waits for a condition to occur. Unlike the await() method, it is not possible to interrupt this call.

long awaitNanos(long nanosTimeout)

Waits for a condition to occur. However, if the notification has not occurred in nanosTimeout nanoseconds, it returns anyway. The return value is an estimate of the timeout remaining; a return value equal or less than zero indicates that the method is returning due to the timeout. As usual, the actual resolution of this method is platform-specific and usually takes milliseconds in practice.

boolean await(long time, TimeUnit unit)

Waits for a condition to occur. However, if the notification has not occurred in the timeout specified by the time and unit pair, it returns with a value of false.

boolean awaitUntil(Date deadline)

Waits for a condition to occur. However, if the notification has not occurred by the absolute time specified, it returns with a value of false.

void signal()

Notifies a thread that is waiting using the Condition object that the condition has occurred.

void signalAll()

Notifies all the threads waiting using the Condition object that the condition has occurred.

Basically, the methods of the Condition interface duplicate the functionality of the wait-and-notify mechanism. A few convenience methods allow the developer to avoid being interrupted or to specify a timeout based on relative or absolute times.

Summary

In this chapter, we introduced the methods of the wait-and-notify mechanism. We also examined the Condition interface, which provides a notification counterpart for the Lock interface.

With these methods of the Object class and Condition interface, threads are able to interoperate efficiently. Instead of just providing protection against race conditions, we now have ways for threads to inform each other about events or conditions without resorting to polling and timeouts.

In later chapters, we examine classes and techniques that provide even higher level support for data synchronization and thread communication.

Example Classes

Here are the class names and Ant targets for the examples in this chapter:

Description

Main Java class

Ant target

Swing Type Tester with wait-and-notify mechanism

javathreads.examples.ch04.example1.SwingTypeTester

ch4-ex1

Swing Type Tester with wait-and-notify mechanism in synchronized blocks

javathreads.examples.ch04.example2.SwingTypeTester

ch4-ex2

Swing Type Tester with condition variables

javathreads.examples.ch04.example3.SwingTypeTester

ch4-ex3



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

[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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required