Countdown Latch

The countdown latch implements a synchronization tool that is very
similar to a barrier. In fact, it can be used instead of a barrier.
It also can be used to implement a functionality that some threading
systems (but not Java) support with semaphores. Like the barrier
class, methods are provided that allow threads to wait for a
condition. The difference is that the release condition is not the
number of threads that are waiting. Instead, the threads are released
when the specified count reaches zero.

The CountDownLatch class provides a method to decrement the
count. It can be called many times by the same thread. It can also be
called by a thread that is not waiting. When the count reaches zero,
all waiting threads are released. It may be that no threads are
waiting. It may be that more threads than the specified count are
waiting. And any thread that attempts to wait after the latch has
triggered is immediately released. The latch does not reset.
Furthermore, later attempts to lower the count will not work.

This interface is pretty simple. The initial count is specified in
the constructor. A couple of overloaded methods are provided for
threads to wait for the count to reach zero. And a couple of methods
are provided to control the count--one to decrement and one to
retrieve the count. The boolean return value for the timeout variant
of the await() method indicates whether the latch was triggered--it returns true if it
is returning because the latch was released.

Exchanger

The exchanger implements a synchronization tool that does not really have
equivalents in any other threading system. The easiest description of
this tool is that it is a combination of a barrier with data passing.
It is a barrier in that it allows pairs of threads to rendezvous with
each other; upon meeting in pairs, it then allow the pairs to
exchange one set of data with each other before separating.

This class is closer to a collection class than a synchronization
tool--it is mainly used to pass data between threads. It is also
very specific in that threads have to be paired up, and a specific
data type must be exchanged. But this class does have its advantages.
Here is its interface:

The exchange() method is called with the data object to be
exchanged with another thread. If another thread is already waiting,
the exchange() method returns with the other
thread's data. If no other thread is waiting, the
exchange() method waits for one. A timeout option
can control how long the calling thread waits.

Unlike the barrier class, this class is very safe to use: it will not
break. It does not matter how many parties are using this class; they
are paired up as the threads come in. Timeouts and interrupts also do
not break the exchanger as they do in the barrier
class; they simply generate an exception condition. The exchanger
continues to pair threads around the exception condition.

Reader/Writer Locks

Sometimes you need to read information from an object in an operation that may take a fairly long time. You
need to lock the object so that the information you read is
consistent, but you don't necessarily need to
prevent another thread from also reading data from the object at the
same time. As long as all the threads are only reading the data,
there's no reason why they
shouldn't read the data in parallel since this
doesn't affect the data each thread is reading.

In fact, the only time we need data locking is when data is being
changed, that is, when it is being written. Changing the data
introduces the possibility that a thread reading the data sees the
data in an inconsistent state. Until now, we've been
content to have a lock that allows only a single thread to access the
data whether the thread is reading or writing, based on the theory
that the lock is held for a short time.

If the lock needs to be held for a long time, it makes sense to
consider allowing multiple threads to read the data simultaneously so
that these threads don't need to compete against
each other to acquire the lock. Of course, we must still allow only a
single thread to write the data, and we must make sure that none of
the threads that were reading the data are still active while our
single writer thread is changing the internal state of the data.

Here are the classes and interfaces in J2SE 5.0 that implement this type of locking:

You create a reader-writer lock by instantiating an object using the
ReentrantReadWriteLock class. Like the
ReentrantLock class, an option allows the locks to be
distributed in a fair fashion. By
"fair," this class means that the
lock is granted on very close to a first-come-first-serve basis. When
the lock is released, the next set of readers/writer is granted the
lock based on arrival time.

Usage of the lock is predictable. Readers should obtain the read lock
while writers should obtain the write lock. Both of these locks are
objects of the Lock class--their interface is
discussed in Chapter 3. There is one major
difference, however: reader-writer locks have different support for
condition variables. You can obtain a condition variable related to
the write lock by calling the newCondition() method; calling that method on a read lock
generates an
UnsupportedOperationException.

These locks also nest, which means that owners of the lock can
repeatedly acquire the locks as necessary. This allows for callbacks
or other complex algorithms to execute safely. Furthermore, threads
that own the write lock can also acquire the read lock. The reverse
is not true. Threads that own the read lock cannot acquire the write
lock; upgrading the lock is not allowed. However, downgrading the
lock is allowed. This is accomplished by acquiring the read lock
before releasing the write lock.

Later in this chapter, we examine the topic of lock starvation in
depth. Reader-writer locks have special issues in this regard.

In this section, we've examined higher-level
synchronization tools provided by J2SE 5.0. These tools all provide
functionality that in the past could have been implemented by the
base tools provided by Java--either through an implementation by
the developer or by the use of third-party libraries. These classes
don't provide new functionality that
couldn't be accomplished in the past; these tools
are written totally in Java. In a sense, they can be considered
convenience classes; that is, they are designed to make development
easier and to allow application development at a higher level.

There is also a lot of overlap between these classes. A
Semaphore can be used to partially simulate a
Lock simply by declaring a semaphore with one
permit. The write lock of a reader-writer lock is practically the
same as a mutually exclusive lock. A semaphore can be used to
simulate a reader-writer lock, with a limited set of readers, simply
by having the reader thread acquire one permit while the writer
thread acquires all the permits. A countdown latch can be used as a
barrier simply by having each thread decrement the count prior to
waiting.

The major advantage in using these classes is that they offload
threading and data synchronization issues. Developers should design
their programs at as high a level as possible and not have to worry
about low-level threading issues. The possibility of deadlock, lock
and CPU starvation, and other very complex issues is mitigated
somewhat. Using these libraries, however, does not remove the
responsibility for these problems from the developer.

Scott Oaks
is a Java Technologist at Sun Microsystems, where he has worked since 1987. While at Sun, he has specialized in many disparate technologies, from the SunOS kernel to network programming and RPCs.