14 Concurrency
Java SE's concurrency APIs provide a powerful, extensible framework of high-performance threading utilities such as thread pools and blocking queues. This package frees the programmer from the need to craft these utilities by hand, in much the same manner the collections framework did for data structures. Additionally, these packages provide low-level primitives for advanced concurrent programming.
The concurrency APIs, which are contained in the package java.util.concurrent, are classes that are designed to be used as building blocks in building concurrent classes or applications. Just as the collections framework simplified the organization and manipulation of in-memory data by providing implementations of commonly used data structures, the concurrency utilities simplify the development of concurrent classes by providing implementations of building blocks commonly used in concurrent designs. The concurrency utilities include a high-performance, flexible thread pool; a framework for asynchronous execution of tasks; a host of collection classes optimized for concurrent access; synchronization utilities such as counting semaphores; atomic variables; locks; and condition variables.
Using the concurrency utilities, instead of developing components such as thread pools yourself, offers a number of advantages:
- Reduced programming effort. It is easier to use a standard class than to develop it yourself.
- Increased performance. The implementations in the concurrency utilities were developed and peer-reviewed by concurrency and performance experts; these implementations are likely to be faster and more scalable than a typical implementation, even by a skilled developer.
- Increased reliability. Developing concurrent classes is
difficult. The low-level concurrency primitives provided by the Java language
(
synchronized
,volatile
,wait()
,notify()
, andnotifyAll()
) are difficult to use correctly, and errors using these facilities can be difficult to detect and debug. By using standardized, extensively tested concurrency building blocks, many potential sources of threading hazards such as deadlock, starvation, race conditions, or excessive context switching are eliminated. The concurrency utilities were carefully audited for deadlock, starvation, and race conditions. - Improved maintainability. Programs that use standard library classes are easier to understand and maintain than those that rely on complicated, homegrown classes.
- Increased productivity. Developers are likely to already understand the standard library classes, so there is no need to learn the API and behavior of ad hoc concurrent components. Additionally, concurrent applications are simpler to debug when they are built on reliable, well-tested components.
In short, using the concurrency APIs to implement a concurrent application can help your program be clearer, shorter, faster, more reliable, more scalable, easier to write, easier to read, and easier to maintain.
The concurrency APIs include the following:
Table 14-1 Concurrency APIs
API | Description |
---|---|
Virtual threads | Virtual Threads are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications. |
Structured concurrency | Structured Concurrency treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. |
Task scheduling framework | The Executor interface standardizes
invocation, scheduling, execution, and control of asynchronous tasks
according to a set of execution policies. Implementations are provided
that enable tasks to be executed within the submitting thread, in a
single background thread (see Executors::newSingleThreadExecutor,
as with events in Swing, in a newly created thread, or in a thread pool
(see Executors::newFixedThreadPool), and
developers can create customized implementations of Executor that support arbitrary
execution policies. The built-in implementations offer configurable
policies such as queue length limits and saturation policy (see RejectedExecutionHandler) that can
improve the stability of applications by preventing runaway resource
use.
|
Fork/join framework | Based on the ForkJoinPool class, this framework is an implementation of Executor. It is designed to efficiently run a large number of tasks using a pool of worker threads. A work-stealing technique is used to keep all the worker threads busy, to take full advantage of multiple processors. |
Concurrent collections | Concurrent collections include Queue , BlockingQueue and BlockingDeque .
|
Atomic variables | Utility classes are provided that atomically manipulate single
variables (primitive types or references), providing high-performance
atomic arithmetic and compare-and-set methods. The atomic variable
implementations in the java.util.concurrent.atomic package
offer higher performance than would be available by using
synchronization (on most platforms), making them useful for implementing
high-performance concurrent algorithms and conveniently implementing
counters and sequence number generators.
|
Synchronizers | Synchronizers are general purpose synchronization classes that facilitate coordination between threads. These include Semaphore, CyclicBarrier, CountdownLatch, Phaser, and Exchanger. |
Locks | While locking is built into the Java language through the
synchronized keyword, there are a number of limitations to built-in
monitor locks. The java.util.concurrent.locks package
provides a high-performance lock implementation with the same memory
semantics as synchronization, and it also supports specifying a timeout
when attempting to acquire a lock, multiple condition variables per
lock, nonnested ("hand-over-hand") holding of multiple locks, and
support for interrupting threads that are waiting to acquire a
lock
|
Nanosecond-granularity timing | The System.nanoTime method enables access to a
nanosecond-granularity time source for making relative time measurements
and methods that accept timeouts (such as the BlockingQueue.offer , BlockingQueue.poll , Lock.tryLock ,
Condition.await , and Thread.sleep )
can take timeout values in nanoseconds. The actual precision of the
System.nanoTime method is
platform-dependent.
|
Thread-local variables | Thread-Local Variables are variables of type ThreadLocal. Unlike "regular" variables, each thread that access a thread-local variable has its own, independently initialized copy of the variable. |