|
Making Concurrency Easier
With its high-performance and scalable concurrent utility classes, JDK 1.5 marks a big step forward for creating concurrent applications in Java
by Brian Goetz
Posted May 31, 2004
Prior to JDK 1.5, the Java platform provided a set of basic primitives for writing concurrent programs: synchronized, volatile, wait(), notify(), and notifyAll(). While these primitives are sufficient to develop sophisticated concurrent classes, they are just that—primitive—and difficult to use properly. Building multithreaded classes on top of Java's low-level concurrency primitives poses many traps for the unwary, and besides, developers don't generally think in terms of wait() and notify() any more than they think in terms of bytecodes. Developers want higher-level building blocks such as scalable, thread-safe collections, semaphores, mutexes, thread pools, locks, and barriers. And this is just what java.util.concurrent provides: a rich set of industrial-strength, high-performance, scalable, concurrency building blocks that can be reused in many different concurrent applications.
Some of the class names in the java.util.concurrent package, such as Executor, ConcurrentHashMap, and Semaphore, will be familiar to developers, as java.util.concurrent is based to a great extent on the widely used util.concurrent package by Doug Lea. Lea was also the specification lead for JSR 166, which produced the java.util.concurrent package. The util.concurrent package provided the JSR 166 Expert Group with an excellent base on which to build, along with the added benefit of four years of experience using util.concurrent in production applications and hundreds of suggestions from users. With this input and experience, the util.concurrent package was refactored to provide a more consistent API, leverage the Collections framework by providing new concurrent collection classes, upgrade the API to support generics, take advantage of improvements in the Java Virtual Machine (JVM), and refine the API to better reflect how these classes are used in practice.
The (audacious) goal of the JSR 166 effort is to do for concurrency what the Collections framework did for data structures, and all indications are that it will be successful in that goal. While java.util.concurrent does not remove the need to understand the issues surrounding multithreading and concurrency, it will free developers from having to deal with the complex details of Java's low-level, concurrency primitives. Multiple threads can freely use classes like ConcurrentHashMap, CopyOnWriteArrayList, and CountDownLatch without concern for synchronization or data races (although the concurrency semantics of these classes are slightly different from their synchronized counterparts).
In developing the java.util.concurrent API and reference implementation, considerable attention was paid to performance and scalability in addition to thread safety. While the Java class library already includes two thread-safe implementations of Map—Hashtable and Collections.synchronizedMap()—these implementations have poor concurrency characteristics. The single monitor used to synchronize all methods can quickly become a scalability bottleneck on multiprocessor systems, and it is often necessary to lock the entire collection during iteration to avoid throwing ConcurrentModificationException. The concurrent collection classes in java.util.concurrent such as ConcurrentHashMap and ConcurrentLinkedQueue are optimized for efficient access by multiple concurrent threads. For example, in ConcurrentHashMap, arbitrarily many read operations may overlap each other, read operations may overlap writes, and up to 16 write operations can overlap each other, providing high-performance, thread-safe Map functionality with unprecedented scalability.
Widely Used Concurrency
Other than the general category of concurrency building blocks, there is no specific theme that unites all the components of java.util.concurrent. Instead, it is a collection of concurrency utility classes that have proven themselves to be useful to a wide variety of concurrent applications, falling into several categories: concurrent collections, task execution and dispatching, synchronizers, atomic variables, and locks. In addition, there are some basic JVM improvements such as nanosecond-granularity time support, accessible through the new System.nanoTime() method; that is a new compare-and-swap primitive (allowing classes to exploit concurrency primitives supported by the underlying processor hardware); and some miscellaneous improvements to the Thread class (such as exposing uncaughtExceptionHandler() on a per-thread basis, which was previously available only as part of ThreadGroup). Let's take a look at these classes.
Back to top
|