By Qusay H. Mahmoud,
March 2005
Java is a multithreaded programming language that makes programming with threads easier, by providing built-in language support for threads. The built-in primitives however, such as synchronized blocks, Object.wait()
, and Object.notify()
are insufficient for many programming tasks. This leads developers to implement their own high-level synchronization facilities, but given the difficulty of concurrency issues, their implementations may not be correct, efficient, or high quality.
The Java 2 Platform, Standard Edition release 5.0 (J2SE 5.0), which is also known as Tiger, has provided a new path to multithreading in the Java programming language. The original mechanisms for coordinating threads with wait()
and notify()
are now enhanced with new and sophisticated mechanisms for working with threads. The new mechanisms are part of the java.util.concurrent
package, which aims to offer a standard set of concurrency utilities that will ease the task of developing multithreaded applications and servers. In addition, such standards will improve the quality of such applications. The package has been defined through the Java Community Process as JSR 166: Concurrency Utilities.
This article provides an overview and an introductory tutorial to the new concurrency mechanisms that have been added to J2SE 5.0. It helps developers get started using the new java.util.concurrent
package and its subpackages.
When writing multithread applications, the issues that may create difficulties are related to data synchronization; these are the errors that make design harder, and such errors are hard to detect. Built-in synchronization (methods and blocks) are fine for many lock-based applications, but they do have their own limitations, such as:
synchronized(obj)
for any accessible object.Such problems can be overcome by using utility classes to control locking, such as Mutex, which is another term for a lock. A mutex, however, doesn't nest like synchronization methods or blocks.
public class Mutex {
public void acquire() throws InterruptedException { }
public void release() { }
public boolean attempt(long msec) throws InterruptedException { }
}
Which then can be used as:
try {
mutex.acquire();
try {
// do something
} finally {
mutex.release();
}
} catch(InterruptedException ie) {
// ...
}
With J2SE 5.0, however, developers do not need to re-invent the wheel by implementing such utility classes. They can use the standardized classes that provide the common building blocks for developing multithreaded applications.
The JSR 166 (Concurrency Utilities) specification aims to standardize a simple, extensible framework that organizes commonly-used utilities for concurrent programming into a small package that can be easily learned by developers. It also aims to provide high-quality implementations consisting of classes and interfaces for atomic variables, locks, barriers, semaphores and condition variables, queues and related collections designed for multithreaded use, and thread pools and a custom execution framework.
The classes and interfaces are similar to those in Doug Lea's util.concurrent
package, which has been available since the late 1990's. The java.util.concurrent
package now includes improved and standardized versions of the main components in Lea's package. Similar packages include Sourceforge's Project: Java HPC Organization: Summary, and the JThreadKit Release 1.0.4.
The java.util.concurrent
package in J2SE 5.0 provides classes and interfaces aiming to simplify the development of concurrent classes and applications by providing high quality implementations of common building blocks used in concurrent applications. The package includes classes optimized for concurrent access, including:
Executor
framework is for standardizing invocation, scheduling, execution, and control of asynchronous tasks according to a set of execution policies. Tasks are allowed to be executed within the submitting thread, in a single background thread (events in Swing), in a newly created thread, or in a thread pool. Also, the built-in implementations offer configurable policies such as queue length limits and saturation policy that can improve the stability of applications by preventing runaway source consumption.
Queue
and BlockingQueue
interfaces, as well as high-performance concurrent implementations of Map
, List
, and Queue
. java.util.concurrent.atomic
package provides classes for automatically manipulating single variables, which can be primitive types or references. The implementations offer higher performance than would be available by using synchronization. This is useful for implementing high performance concurrent algorithms and for implementing counters and sequence number generators.
synchronized
keyword can be used for locking, but as mentioned earlier, this built-in monitor has its own limitations. The java.util.concurrent.locks
provides a high-performance lock implementation, and also allows developers to specify a timeout when attempting to acquire a lock, multiple condition variables per lock, non-nested scoped locks, and support for interrupting threads that are waiting to acquire a lock.
java.lang.System.nanotime()
method that provides high-precision timing facilities has been added to enable access to nanosecond-granularity time source for making relative time measurements, and methods that accept timeouts such as BlockingQueue.offer()
, BlockingQueue.poll()
, Lock.tryLock()
, Condition.wait()
, and Thread.sleep()
. Note that the precision of nanotime()
is platform-dependent.Using the new java.util.concurrent
package offers a number of advantages, including:
synchronized
, wait()
, and notify()
) are difficult to use correctly...and errors are not easy to detect or debug. On the other hand, the concurrency utilities are standardized and extensively tested against deadlock, starvation, or race conditions.
The java.util.concurrent
package provides utility classes commonly useful for data synchronization, including:
Semaphore
: A classic concurrency toolCyclicBarrier
: A resettable multiway synchronization point (useful for parallel programming)CountDownLatch
: A utility for blocking until a given number of signals, events, or conditions holdExchanger
: Allows two threads to exchange objects at a rendezvous point, and can be useful in pipeline designsSemaphore: A semaphore is a classic concurrency control construct. It is basically a lock with an attached counter. Similar to the Lock
interface, it can be used to prevent access if the lock is granted. The Semaphore
class keeps track of a set of permits. Each acquire()
blocks if necessary until a permit is available, and then takes it. Each release()
adds a permit. Note that no actual permit objects are used, the Semaphore
maintains a count of the number available and acts accordingly. A semaphore with a counter of one can serve as a mutual exclusion lock.
Barrier: This is a synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. The interface to the barrier is the CyclicBarrier
class, called cyclic because it can be re-used after the waiting threads are released. This is useful for parallel programming.
CountDown Latch: A latch is a condition starting out false, but once set true remains true forever. The java.util.concurrent.CountDownLatch class serves as a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. This synchronization tool is similar to a barrier in the sense that it provides methods that allow threads to wait for a condition, but 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 initial count is specified in the constructor; the latch does not reset and later attempts to lower the count will not work.
Exchanger: Allows two threads to exchange objects at a rendezvous point, and can be useful in pipeline designs. Each thread presents some object on entry to the exchange()
method and receives the object presented by the other thread on return. As an example, consider the classical consumer-producer problem (two entities share a channel); a description of the problem and a sample solution using built-in synchronization techniques ( wait()
and notify()
) is discussed in the Java tutorial and can be found here.
This can be rewritten in J2SE 5.0 using Exchanger
as follows.
try { // Consumer thread
while (currentBuffer != null) {
takeFromBuffer(currentBuffer);
if (currentBuffer.empty())
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) {
// ... handle ...
}
try { // Producer thread
while (currentBuffer != null) {
addToBuffer(currentBuffer);
if (currentBuffer.full())
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) {
// ... handle ...
}
For more information on the java.util.concurrent
package, see the package description. In addition, sample test programs using the above J2SE 5.0 synchronizers can be found in the article Getting to Know Synchronizers.
The java.util.concurrent.locks
provides classes for locking and waiting for conditions that are distinct from built-in synchronization and monitors. This framework allows greater flexibility in the use of locks and conditions at the price of an awkward syntax. This package provides reader/writer locks.
When using a thread to read data from an object, you do not necessarily need to prevent another thread from reading data at the same time. So long as the threads are only reading and not changing data, there is no reason why they cannot read in parallel. The J2SE 5.0 java.util.concurrent.locks
package provides classes that implement this type of locking. The ReadWriteLock
interface maintains a pair of associated locks, one for read-only and one for writing. The readLock()
may be held simultaneously by multiple reader threads, so long as there are no writers. The writeLock()
is exclusive. While in theory, it is clear that the use of reader/writer locks to increase concurrency leads to performance improvements over using a mutual exclusion lock. However, this performance improvement will only be fully realized on a multi-processor and the frequency that the data is read compared to being modified as well as the duration of the read and write operations.
For more information on the java.util.concurrent.locks
package, see the package description.
The java.util.concurrent.atomic
package provides classes that support lock-free thread-safe programming on single variables. The classes here extend the notion of volatile
values, fields, and array elements.
The classes in this package allow multiple operations to be treated atomically. As an example, a volatile integer cannot be used with the ++
operator because this operator contains multiple instructions. The AtomicInteger
class has a method that allows the integer it holds to be incremented atomically without using synchronization. Atomic classes, however, can be used for more complex tasks, such as building code that requires no synchronization. Here is a sample sequence number generator that use the AtomicLong
class:
import java.util.concurrent.atomic.*;
public class SequenceGenerator {
private AtomicLong number = new AtomicLong(0);
public long next() {
return number.getAndIncrement();
}
}
Instances of classes AtomicBoolean
, AtomicInteger
, AtomicLong
, and AtomicReference
each provide access and updates to a single variable of the corresponding type. In addition, each class provides utility methods for that type, such as atomic increment methods.
The getAndSet()
method of these classes automatically sets the variable to a new value and returns the previous value without any synchronization locks. The compareAndSet()
and weakCompareAndSet()
methods are conditional modifier methods: they take two arguments, the value the data is expected to have when the method starts, and a new value to set the data to. In other words, these methods have the ability to set a value automatically only if the current value is an expected value.
For more information on the java.util.concurrent.atomic
package, see the package description.
J2SE 5.0 has added the java.util.concurrent
package that provides standardized concurrency utilities. While the classes provided do not provide new functionality that couldn't be accomplished in the past, they are convenience classes designed to make development easier by providing high-quality implementations for data synchronization mechanisms. This package does for concurrent programming what the collection framework has done to data structures -- essentially freeing the developer from re-inventing the wheel with possibly incorrect and inefficient implementations.
Using the java.util.concurrent package
can help you make your applications shorter, cleaner, faster, more reliable, more scalable, easier to write and read, and thus easier to maintain. However, it is important to note that using the package doesn't remove the responsibility of the developer from ensuring that the applications are free of deadlock, or CPU starvation. Developers still need to design their applications with concurrency and data synchronization issues in mind.
There is overlap between the classes. For example, a semaphore with one permit can be used to simulate a lock. Therefore, in order to use the new package in an effective and safe manner, several details need to be explored and clearly understood. It is a good idea to review the packages and the classes they offer.
Special thanks to Martin Buchholz of Sun Microsystems, whose feedback helped me improve this article.