A "main" {@code ForkJoinTask} begins execution when it isexplicitly submitted to a {@link ForkJoinPool}, or, if not already engaged in a ForkJoin computation, commenced in the {@link ForkJoinPool#commonPool()} via {@link #fork}, {@link #invoke}, or related methods. Once started, it will usually in turn start other subtasks. As indicated by the name of this class, many programs using {@code ForkJoinTask} employ only methods {@link #fork} and{@link #join}, or derivatives such as {@link #invokeAll(io.netty.util.internal.chmv8.ForkJoinTask) invokeAll}. However, this class also provides a number of other methods that can come into play in advanced usages, as well as extension mechanics that allow support of new forms of fork/join processing.
A {@code ForkJoinTask} is a lightweight form of {@link java.util.concurrent.Future}. The efficiency of {@code ForkJoinTask}s stems from a set of restrictions (that are only partially statically enforceable) reflecting their main use as computational tasks calculating pure functions or operating on purely isolated objects. The primary coordination mechanisms are {@link #fork}, that arranges asynchronous execution, and {@link #join}, that doesn't proceed until the task's result has been computed. Computations should ideally avoid {@code synchronized} methods or blocks, and shouldminimize other blocking synchronization apart from joining other tasks or using synchronizers such as Phasers that are advertised to cooperate with fork/join scheduling. Subdividable tasks should also not perform blocking I/O, and should ideally access variables that are completely independent of those accessed by other running tasks. These guidelines are loosely enforced by not permitting checked exceptions such as {@code IOExceptions} to bethrown. However, computations may still encounter unchecked exceptions, that are rethrown to callers attempting to join them. These exceptions may additionally include {@link java.util.concurrent.RejectedExecutionException} stemming from internal resourceexhaustion, such as failure to allocate internal task queues. Rethrown exceptions behave in the same way as regular exceptions, but, when possible, contain stack traces (as displayed for example using {@code ex.printStackTrace()}) of both the thread that initiated the computation as well as the thread actually encountering the exception; minimally only the latter.
It is possible to define and use ForkJoinTasks that may block, but doing do requires three further considerations: (1) Completion of few if any other tasks should be dependent on a task that blocks on external synchronization or I/O. Event-style async tasks that are never joined (for example, those subclassing {@link CountedCompleter}) often fall into this category. (2) To minimize resource impact, tasks should be small; ideally performing only the (possibly) blocking action. (3) Unless the {@link ForkJoinPool.ManagedBlocker} API is used, or the number of possiblyblocked tasks is known to be less than the pool's {@link ForkJoinPool#getParallelism} level, the pool cannot guarantee thatenough threads will be available to ensure progress or good performance.
The primary method for awaiting completion and extracting results of a task is {@link #join}, but there are several variants: The {@link java.util.concurrent.Future#get} methods support interruptible and/or timedwaits for completion and report results using {@code Future}conventions. Method {@link #invoke} is semanticallyequivalent to {@code fork(); join()} but always attempts to beginexecution in the current thread. The "quiet" forms of these methods do not extract results or report exceptions. These may be useful when a set of tasks are being executed, and you need to delay processing of results or exceptions until all complete. Method {@code invokeAll} (available in multiple versions)performs the most common form of parallel invocation: forking a set of tasks and joining them all.
In the most typical usages, a fork-join pair act like a call (fork) and return (join) from a parallel recursive function. As is the case with other forms of recursive calls, returns (joins) should be performed innermost-first. For example, {@code a.fork();b.fork(); b.join(); a.join();} is likely to be substantially moreefficient than joining {@code a} before {@code b}.
The execution status of tasks may be queried at several levels of detail: {@link #isDone} is true if a task completed in any way(including the case where a task was cancelled without executing); {@link #isCompletedNormally} is true if a task completed withoutcancellation or encountering an exception; {@link #isCancelled} istrue if the task was cancelled (in which case {@link #getException}returns a {@link java.util.concurrent.CancellationException}); and {@link #isCompletedAbnormally} is true if a task was eithercancelled or encountered an exception, in which case {@link #getException} will return either the encountered exception or{@link java.util.concurrent.CancellationException}.
The ForkJoinTask class is not usually directly subclassed. Instead, you subclass one of the abstract classes that support a particular style of fork/join processing, typically {@link RecursiveAction} for most computations that do not return results,{@link RecursiveTask} for those that do, and {@link CountedCompleter} for those in which completed actions triggerother actions. Normally, a concrete ForkJoinTask subclass declares fields comprising its parameters, established in a constructor, and then defines a {@code compute} method that somehow uses the controlmethods supplied by this base class.
Method {@link #join} and its variants are appropriate for useonly when completion dependencies are acyclic; that is, the parallel computation can be described as a directed acyclic graph (DAG). Otherwise, executions may encounter a form of deadlock as tasks cyclically wait for each other. However, this framework supports other methods and techniques (for example the use of {@link java.util.concurrent.Phaser}, {@link #helpQuiesce}, and {@link #complete}) that may be of use in constructing custom subclasses for problems that are not statically structured as DAGs. To support such usages a ForkJoinTask may be atomically tagged with a {@code short}value using {@link #setForkJoinTaskTag} or {@link #compareAndSetForkJoinTaskTag} and checked using {@link #getForkJoinTaskTag}. The ForkJoinTask implementation does not use these {@code protected} methods or tags for any purpose, but theymay be of use in the construction of specialized subclasses. For example, parallel graph traversals can use the supplied methods to avoid revisiting nodes/tasks that have already been processed. (Method names for tagging are bulky in part to encourage definition of methods that reflect their usage patterns.)
Most base support methods are {@code final}, to prevent overriding of implementations that are intrinsically tied to the underlying lightweight task scheduling framework. Developers creating new basic styles of fork/join processing should minimally implement {@code protected} methods {@link #exec}, {@link #setRawResult}, and {@link #getRawResult}, while also introducing an abstract computational method that can be implemented in its subclasses, possibly relying on other {@code protected} methodsprovided by this class.
ForkJoinTasks should perform relatively small amounts of computation. Large tasks should be split into smaller subtasks, usually via recursive decomposition. As a very rough rule of thumb, a task should perform more than 100 and less than 10000 basic computational steps, and should avoid indefinite looping. If tasks are too big, then parallelism cannot improve throughput. If too small, then memory and internal task maintenance overhead may overwhelm processing.
This class provides {@code adapt} methods for {@link Runnable}and {@link java.util.concurrent.Callable}, that may be of use when mixing execution of {@code ForkJoinTasks} with other kinds of tasks. When all tasks areof this form, consider using a pool constructed in asyncMode.
ForkJoinTasks are {@code Serializable}, which enables them to be used in extensions such as remote execution frameworks. It is sensible to serialize tasks only before or after, but not during, execution. Serialization is not relied on during execution itself. @since 1.7 @author Doug Lea
|
|
|
|
|
|
|
|
|
|
|
|