JDK 20 Introduces Structured Concurrency
Java has supported thread concurrency, or concurrent programming, for multitasking since the beginning (version 1.0). The java.lang.Thread class represents a thread of execution in a Java program. The JVM (Java Virtual Machine) allows an application to run multiple threads concurrently. Tasks can be broken down into subtasks, and each subtask runs in its own thread. The java.util.concurrent.ExecutorService API introduced in JDK 5 makes it possible to run subtasks concurrently. Further, the lightweight virtual threads introduced in JDK 10 make it easier to run and maintain high throughput, concurrent applications.
Each subtask within a task can succeed or fail independently. If all subtasks within a task succeed, all is fine, but if one or more of the subtasks were to fail, the task as a whole has failed and needs to be canceled in addition to canceling the other concurrent subtasks. The structure of a task and its subtasks is only a logical structure that a program developer envisages, and there is no real built-in coordination between a task and its subtasks. Thread leaks could occur if some of the subtasks running within their independent threads have failed while others continue to run unmanaged. ExecutorService allows unstructured patterns of concurrency without any constraints on, or order management of the threads involved. Trying to track individual threads and manage them explicitly makes the program too complex for a developer to develop or manage. The java.util.concurrent.ForkJoin API does provide a structure for concurrent tasks, but the API is designed mainly for compute-intensive tasks, and not for a task that computes the result of multiple I/O operations with each I/O operation running as a subtask within its own thread.
JDK 20 introduces the jdk.incubator.concurrent.StructuredTaskScope API for structured concurrency. StructuredTaskScope creates a structured task scope using the other threads that can be forked to run subtasks. The subtasks run within the scope of the main thread that created the subtask threads. This ensures a hierarchical task-subtasks relationship. The task scope waits for the subtasks within the sub threads to finish before processing results. Two subclasses, ShutdownOnSuccess and ShutdownOnFailure are provided to manage common use cases. The main task does not have to depend on all subtasks to finish in order to process the result. If successful completion of one of the subtasks suffices for the main task to process results, and the ShutdownOnSuccess collects the first result and shuts down the task scope. The other use case is that the main task depends on the results of all the subtasks; failure of one or more subtasks indicates the failure of the main tasks as a whole. In this scenario, the ShutdownOnFailure subclass catches the first exception, or failure, and shuts down the task scope.
The main benefits of the structured concurrency are listed below.
Prevents Thread Leaks and Cancellation Delays
Thread leaks are prevented by implementing a cancellation policy using the ShutdownOnFailure. If the main task depends on the result of all the subtasks and one of the subtasks fails, the other subtasks are automatically canceled. If the main task thread is interrupted, the subtask threads are canceled automatically.
Clear Code Structure
Structured concurrency ensures a clear code structure. The main task sets up the subtasks, and waits for the results. On success, the main task processes the results and shuts down the subtasks, and on failure the subtasks are shut down regardless. A clear code structure makes it easier to maintain code.
The task hierarchy is observable in the thread dump. Observability facilitates management and reliability of tasks and subtasks.
In summary, the new incubating API for structured concurrency introduces a new style of concurrent programming with several additional benefits. Structured concurrency is a level above the basic concurrency provided by threads.