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.
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 andnanos
nanoseconds, it returns anyway. This method must be called from a synchronized method or block. Note that, just like thesleep()
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.
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:
The first thread tests the condition and confirms that it must wait.
The second thread sets the condition.
The second thread calls the
notify()
method; this goes unheard since the first thread is not yet waiting.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:
Thread 1 calls a method that acquires the synchronization lock.
Thread 1 examines a state flag and determines that the data is not in the desired state.
Thread 1 calls the
wait()
method, which frees the lock.Thread 2 calls a method that acquires the same synchronization lock.
Thread 3 calls a method that blocks waiting for the lock.
Thread 2 sets the state flag and calls the
notify()
method.Thread 2 finishes its method and frees the lock.
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.
Thread 3 exits without needing to wait.
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.
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.
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 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
andunit
pair, it returns with a value offalse
.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.
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.
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 | | ch4-ex1 |
Swing Type Tester with wait-and-notify mechanism in synchronized blocks | | ch4-ex2 |
Swing Type Tester with condition variables | | 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.
Get Java Threads, 3rd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.