Concurrent processes, i.e. threads, are extremely useful tools
but, as we see in the discussions of synchronization in Chapter
8: Java, can be tricky to handle when the processes must interact
with each other. Java 5.0 came with many additional tools for the
programmer to use when threading.
The Concurrency
Utilities - Java Language Programming Guide gives an overview
of the new tools. This article - Getting
to Know Synchonizers - Java Tech Tips - Feb.26.2005 - gives
a nice introduction to the follwoing four synchronization tools:
Semaphores:
The wait-notify system in standard Java is not the only way that
thread synchronization can be done. A classic approach in other
languages is the semaphore,
which is a protected variable allows up to a given N
number of threads to access a resource simultaneously. As described
in the tutorial,
an instance of the new Java Semphore class
can be used to limit how many many treads can invoke a particular
method at any one time.
Barriers:
Say that you are simulating a system of many mutally interacting
particles, each of whose motion depends on the position of the other
particles. You assign a thead to each particle to calculate its
velocity, position, etc. for an increment in time based on the sum
of the forces from the other particles. You clearly have to constrain
each thread to calculate only one step and then wait for all the
other particle to "catch up". Otherwise, some threads
will get far ahead of the others and calculate their particle motions
incorrectly based on previous positions of the other particles rather
than on where those other particles would actually be at the same
time step.
So a given thread should do its calculation and then wait until
all the other threads are finished before starting to calculate
the next step. This could be done with a barrier synchronization
technique. This term implies that a thread is stopped at the barrier
until all threads reach it. Once all have reached it, the barrier
is lowered and they all start a new round of step calculations and
all again wait at the barrier after they finish their work.
The concurrency utilities offers the CyclicBarrier
class to provide this tool. (It's called cyclic because it can be
reused after each time the waiting threads are released by it.)
The class includes the option of running a thread when once all
the threads reach the barrier. The tutorial
uses the case of adding up the rows of matrix.
Latches:
The CountDownLatch class allows one
to prevent a set of threads from running until you are ready for
them to. For example, you might want to create the threads and then
do some initialization tasks before starting them all simultaneously.
The tutorial
shows how to use the CountDownLatch to
start several threads simultaneously and also to stop them all at
the same time.
The join() method in the Thread
class can also be used to allow for two or more threads to finish
togehter. However, latches can be used to create several places
where threads must wait before proceeding with further processing.
Exchangers:
Say that you want to create a producer/consumer
system in which the producer fills up a buffer, passes the filled
buffer to the consumer, which then begins to empty that buffer for
its own purposes. Meanwhile, the producer could be filling a new
buffer. Rather than creating a new buffer object each time, the
consumer could pass the producer an old buffer that it had finished
emptying.
The java.util.concurrent.Exchanger
class provides for this exchange of objects. The tutorial
provides a producer/consumer example that illustrates this.
More new concurrency Tools:
In Chapter
10: Java: The Concurrency Utilities, we discuss the new Executor
framework, which relieves the programmer of threading toil such
as creating thread pools, killing a thread safely, etc. The Callable
interface improves on Runnable by allowing
for the return of data and for throwing and catching an exception
from a thread.
References & Web Resources
Most recent update: Oct. 6, 2005
|