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

4.2 Condition Variables - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

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

    Previous Section  < Day Day Up >  Next Section