Preface
Fork join framework is the introduction of java 7 framework, the introduction of this framework is mainly to improve the ability of parallel computing.
Fork join has two main steps, the first is fork, a large task into many small tasks, the second is join, the results of the first task join up to generate the final result. If there is no return value in the first step, join will wait until all the small tasks are finished.
Here we explain the fork join framework in detail from these three aspects.
ForkJoinPool
ForkJoinPool is an implementation of the ExecutorService that provides some convenient management methods for worker threads and thread pools.
|
|
A work thread can only handle one task at a time, but instead of creating a separate thread for each task, ForkJoinPool uses a special data structure double-ended queue to store the tasks.
By default, a work thread takes tasks from the head of the queue assigned to it. If this queue is empty, then this work thread will take tasks from the tail of other task queues to execute, or from the global queue. This design makes full use of the work thread’s performance and improves concurrency.
Here is a look at how to create a ForkJoinPool.
The most common way is to use ForkJoinPool.commonPool() to create it. commonPool() provides a common default thread pool for all ForkJoinTask.
|
|
Another way is to use the constructor.
|
|
The argument here is the parallelism level, and 2 means that the thread pool will use 2 processor cores.
ForkJoinWorkerThread
ForkJoinWorkerThread is the worker thread used in ForkJoinPool.
Unlike normal threads it defines two variables.
One is the ForkJoinPool to which this worker thread belongs. The other is a Queue that supports work-stealing mechanics.
Look at its run method again.
|
|
To put it simply, it takes the task from the Queue and executes it.
ForkJoinTask
ForkJoinTask is the type of task that runs in ForkJoinPool. We usually use two of its subclasses: RecursiveAction and RecursiveTask.
They both define a compute() method that needs to be implemented to achieve specific business logic. The difference is that RecursiveAction is only used to execute tasks, while RecursiveTask can have return values.
Since both classes come with Recursive, the specific implementation logic will also be related to recursion, so let’s take an example of using RecursiveAction to print a string.
|
|
The above example uses dichotomy to print the string.
Let’s look at another example of a RecursiveTask.
|
|
Much like the above example, but here we need to have return values.
Submitting Task in ForkJoinPool
With the two tasks above, we can submit in the ForkJoinPool.
In the above example, we use invoke to commit, and invoke will wait for the result of the task execution.
If we don’t use invoke, we can also replace it with fork() and join().
fork() submits the task to the pool, but does not trigger execution. join() will actually execute and get the returned result.