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.
Limitations of Built-in Synchronization in J2SE 1.4.x
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:
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.
Which then can be used as:
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.
JSR 166 Concurrency Utilities
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. Applications of some of the classes can be found in Doug Lea's book Concurrent Programming with Java (Second Edition). 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.
Overview of the Concurrency Utilities
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:
Using the new
java.util.concurrent package offers a number of advantages, including:
java.util.concurrent Package
The
java.util.concurrent package provides utility classes commonly useful for data synchronization, including:
Semaphore: 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.
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.
java.util.concurrent.locks Package
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.
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.
java.util.concurrent.atomic Package
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:
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.
Conclusion
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.
For More Information
Acknowledgments
Special thanks to Martin Buchholz of Sun Microsystems, whose feedback helped me improve this article.
| ||||||||||||||||||||
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||