At JDK 1.7, the standard class library was added ForkJoinPool
as an implementation of the Fork/join thread pool. Fork has a forked meaning in English, and Join has the meaning of merging . ForkJoinPool
the same is true:fork forks large tasks into small tasks, then makes small tasks,joins is the result of getting small tasks, then merges, and merges the results as the result of a large task--and this is a recursive process-- Because the task is large enough, you can fork the task multilevel until the task is small enough.
Thus, it ForkJoinPool
can satisfy the need of parallel implementation of the divide-and- Conquer algorithm (Divide-and-conquer) .
ForkJoinPool
The class diagram is as follows:
You can see ForkJoinPool
that the interface is implemented ExecutorService
, so the first ForkJoinPool
is a thread pool . Thus Runnable
and Callable
types of tasks ForkJoinPool
can also be submit
performed through, invokeAll
and invokeAny
other methods. But the standard class library also ForkJoinPool
defines a new task, which is ForkJoinTask<V>
.
ForkJoinTask
Related class diagram:
ForkJoinTask<V>
Used to define fork/join tasks--to complete the work of splitting large tasks into small tasks and merging results. In general, we do not need to inherit directly ForkJoinTask<V>
, but inherit its subclass RecursiveAction
and RecursiveTask
implement the corresponding abstract method-- compute
. RecursiveAction
This is a fork/join task with no return value, so using such a task does not produce results, it does not involve merging the results, but RecursiveTask
is a fork/join task with a return value, and using such a task requires us to merge the results. By means of which fork
we can produce subtasks and execute them join
, we can get the results of subtasks by means of methods.
ForkJoinPool
There are three ways to do this ForkJoinTask
:
invoke
Method:
invoke
method is used to execute a task with a return value (usually inherited from RecursiveTask
), and the method is blocked until the task finishes executing, and the method stops blocking and returns the result of the task's execution.
submit
Method:
In addition to the ExecutorService
inherited submit
method, the method used to ForkJoinPool
execute ForkJoinTask
is defined-typically submit
the submit
method is used to execute a return value ForkJoinTask
(usually inherited from RecursiveTask
). The method is non-blocking, and the task is returned immediately after the call ForkJoinPool
to execute, and returns the task that has been committed to execution-the ForkJoinPool
interface is realized by the class diagram ForkJoinTask
Future
, so it can be passed directly through the TAS K to interact with the submitted task.
execute
Method:
In addition to Executor
the method obtained, the method execute
ForkJoinPool
used to execute is defined ForkJoinTask
execute
-Generally the execute
method is used to execute without the return value ForkJoinTask
(usually inherited from RecursiveAction
), the method is also non-blocking.
Now let's practice ForkJoinPool
the function: Calculate the value of π.
The value of π is computed by a polynomial method, i.e.:
Π= 4 * (1-1/3 + 1/5-1/7 + 1/9-...)
The greater the number of items in the polynomial, the more accurate the calculated π value.
First we define what is used to estimate π PiEstimateTask
:
StaticClass Piestimatetask extends Recursivetask<double> {Private finalLongBeginPrivate finalLongEndPrivate finalLong threshold;Critical value for split taskPublic Piestimatetask (LongBeginLongEndLong threshold) {This.Begin =BeginThis.End =EndThis.threshold = threshold; } @OverrideProtected Double Compute () {if (End-Begin <= Threshold) {int sign =1;Symbol, take 1 or-1Double result =0.0;for (Long i =Begin I <End i++) {result + = sign/(I *2.0 +1); sign =-sign; }return result *4; }Split tasklong middle = (begin + end)/ Span class= "Hljs-number" >2; Piestimatetask lefttask = new piestimatetask (begin, Middle, Threshold); Piestimatetask righttask = new piestimatetask (middle, end, Threshold); Lefttask.fork (); //asynchronously executes Lefttask righttask.fork (); //asynchronously executes righttask double Leftresult = LeftTask.join (); //blocked until Lefttask execution is complete return result double Rightresult = Righttask.join (); Span class= "Hljs-comment" >//blocked until Righttask execution has finished returning results return Leftresult + rightresult; Span class= "hljs-comment" >//Merge result}}
Then we use ForkJoinPool
the invoke
execution PiEstimateTask
:
PublicClass Forkjoinpooltest {public static void Main (String[] args) throws Exception {forkjoinpool forkjoinpool = new Forkjoinpool (4); //calculates 1 billion items, the threshold value of the split task is 10 million piestimatetask task = new piestimatetask (0, 1_000_000_000, 10_000_000); double pi = forkjoinpool.invoke (task); //blocked until the task has been executed to return the result System.out. println (the value of the "π:" + pi); Forkjoinpool.shutdown (); //send closed instruction to thread pool}}
Operation Result:
We can also use submit
methods to execute tasks asynchronously (the submit
object that the method returns in this case is the task that the task was submitted to):
PublicStaticvoid Main (string[] args)throws Exception {forkjoinpool forkjoinpool = new ForkJoinPool (4); Piestimatetask task = new piestimatetask ( 0, 1_000_000_000, 10_000_000); Future<double> future = Forkjoinpool.submit (task); Span class= "hljs-comment" >//does not block double pi = Future.get (); System.out. println ( "π value:" + pi); System.out. println ( "future points to the object is task:" + (future = = task)"); Forkjoinpool.shutdown (); //send closed instruction to thread pool}
Operation Result:
It is important to note that selecting a critical value for a suitable partition task has a critical impact on the efficiency of the ForkJoinPool
task. The threshold value is too large, the task segmentation is not thin enough, the CPU can not be fully utilized, the threshold is too small, then the task is too fragmented, may produce too many subtasks, resulting in excessive switching between threads and aggravating the burden of GC to affect the efficiency. Therefore, it is necessary to choose the critical value of a suitable partition task according to the actual application scenario.
ForkJoinPool
In contrast ThreadPoolExecutor
, there is a very important feature (the advantage) is the ForkJoinPool
ability to have work-stealing (Work-stealing). The implementation of the so-called work-stealing ForkJoinPool
is that each thread in the thread pool has a task queue that does not affect each other (a double-ended queue), and each thread takes a task out of the team header of its own task queue each time, and if a thread corresponds to a queue that is empty and in an idle state, While other threads have tasks in the queue that need to be processed but the thread is working, then the idle thread can take a task from the queue of the other thread to help run it-it feels like a free thread is going to steal someone's job to run, so it's called "work-stealing."
The work-stealing scenario is that different tasks are time-consuming, which means that some tasks need to run for a long time, and some tasks run quickly, in which case it is appropriate to use work-stealing, but if the task is time-consuming, then Work-stealing is not suitable, because stealing a task also requires a preemption lock, which can cause additional time consumption, and each thread maintains a double-ended queue that also causes greater memory consumption. So ForkJoinPool
it is not ThreadPoolExecutor
a substitute, but a ThreadPoolExecutor
supplement to it.
Summarize:
ForkJoinPool
And ThreadPoolExecutor
both are ExecutorService
(thread pools), but ForkJoinPool
the unique point is:
ThreadPoolExecutor
Can only execute Runnable
and Callable
task, ForkJoinPool
not only can execute and task, but also can perform fork/join-------- Runnable
Callable
ForkJoinTask
to meet the need of parallel implementation of divide and conquer algorithm;
ThreadPoolExecutor
The order in which the tasks are executed is performed in the order in which they are in the Shared queue, so the subsequent tasks need to wait for the previous task to execute before they are executed, and ForkJoinPool
each thread has its own task queue, and on this basis implements the Work-stealing function, which in some cases ForkJoinPool
can increase the concurrency efficiency more greatly.
Fork/join-type thread pool and work-stealing algorithm