本文翻译自http://tutorials.jenkov.com/java-util-concurrent/java-fork-and-join-forkjoinpool.html,人工翻译,仅供学习交流。
Java Fork and Join using ForkJoinPool
在Java 7中添加了ForkJoinPool,ForkJoinPool类似于Java ExecutorService,但有一个区别。ForkJoinPool使任务很容易把他们的工作分割成更小的可以提交给ForkJoinPool的任务。只要任务能够拆分,可以继续将它们的工作拆分为更小的子任务。这听起来可能有点抽象,所以在这个分叉和加入教程中,我将解释ForkJoinPool如何工作,以及如何拆分任务。
Fork和Join的解释
在我们看ForkJoinPool之前,我想解释一下fork和join原理是如何工作的。fork和join原理由递归执行的两个步骤组成。这两个步骤是fork步骤和join步骤。
Fork
使用fork和join原理的任务可以将自己拆分为更小的可以并发执行的子任务,如下图所示:
通过将自己分解成子任务,每个子任务可以由不同的cpu并行执行,或者是同一个CPU上的不同线程。只有当任务分配的工作足够大时,任务才会将自己分解成子任务。将一个任务分解成子任务会产生开销,因此,对于少量的工作,这个开销可能比并发执行子任务获得的加速更大。
将一个任务拆分为子任务有意义的限制也称为阈值。由每个任务来决定一个合理的阈值。这在很大程度上取决于工作的类型。
Join
当一个任务将自己分解成子任务时,该任务等待所有子任务完成执行。
一旦子任务完成执行,任务可以将所有结果合并为一个结果。如下图所示:
当然,不是所有类型的任务都可以返回结果。如果任务不返回结果,那么任务只会等待它的子任务完成,这时不会发生结果合并。
ForkJoinPool
ForkJoinPool是一个设计用来很好地处理fork-and-join任务分割的特殊线程池,ForkJoinPool位于java.util.concurrent包中,所以完整的类名是java.util.concurrent.ForkJoinPool。
创建ForkJoinPool
使用ForkJoinPool的构造函数创建ForkJoinPool。作为ForkJoinPool构造函数的参数可以指定需要的并行级别。并行级别是指在传递给ForkJoinPool的任务上,希望并发运行多少线程或cpu。以下是一个创建ForkJoinPool的示例:
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
这个例子创建了一个并行级别为4的ForkJoinPool。
提交任务到ForkJoinPool
将任务提交给ForkJoinPool类似于将任务提交给ExecutorService。您可以提交两种类型的任务,不返回任何结果的任务(一个“动作”)和一个返回结果的任务(一个“任务”)。这两种类型的任务由RecursiveAction和RecursiveTask类表示。以下部分将介绍如何使用这两个任务以及如何提交它们。
RecursiveAction类
RecursiveAction是一个不返回任何值的任务,它只是做一些工作,例如将数据写入磁盘,然后退出。
RecursiveAction可能仍然需要将其工作分解成更小的由独立的线程或cpu执行的块。
通过子类化递归动作来实现递归动作。这里是一个RecursiveAction的例子:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;
public class MyRecursiveAction extends RecursiveAction {
private long workLoad = 0;
public MyRecursiveAction(long workLoad) {
this.workLoad = workLoad;
}
@Override
protected void compute() {
//if work is above threshold, break tasks up into smaller tasks
if(this.workLoad > 16) {
System.out.println("Splitting workLoad : " + this.workLoad);
List<MyRecursiveAction> subtasks =
new ArrayList<MyRecursiveAction>();
subtasks.addAll(createSubtasks());
for(RecursiveAction subtask : subtasks){
subtask.fork();
}
} else {
System.out.println("Doing workLoad myself: " + this.workLoad);
}
}
private List<MyRecursiveAction> createSubtasks() {
List<MyRecursiveAction> subtasks =
new ArrayList<MyRecursiveAction>();
MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2);
MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2);
subtasks.add(subtask1);
subtasks.add(subtask2);
return subtasks;
}
}
这个例子非常简单,MyRecursiveAction只是将一个虚构的工作负载作为其构造函数的参数。如果工作负载超过某个阈值,工作被分解成子任务,这些子任务也被调度执行(通过子任务的fork()方法)。如果工作负载低于某个阈值,则工作将由MyRecursiveAction 自身执行。
你可以像这样调度MyRecursiveAction的执行:
MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24);
forkJoinPool.invoke(myRecursiveAction);
RecursiveTask类
RecursiveTask是一个会返回结果的任务。它可能会把工作分成更小的任务,然后把这些小任务的结果合并成一个集体的结果。拆分和合并可能发生在几个层次上。下面是一个RecursiveTask的例子:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
public class MyRecursiveTask extends RecursiveTask<Long> {
private long workLoad = 0;
public MyRecursiveTask(long workLoad) {
this.workLoad = workLoad;
}
protected Long compute() {
//if work is above threshold, break tasks up into smaller tasks
if(this.workLoad > 16) {
System.out.println("Splitting workLoad : " + this.workLoad);
List<MyRecursiveTask> subtasks =
new ArrayList<MyRecursiveTask>();
subtasks.addAll(createSubtasks());
for(MyRecursiveTask subtask : subtasks){
subtask.fork();
}
long result = 0;
for(MyRecursiveTask subtask : subtasks) {
result += subtask.join();
}
return result;
} else {
System.out.println("Doing workLoad myself: " + this.workLoad);
return workLoad * 3;
}
}
private List<MyRecursiveTask> createSubtasks() {
List<MyRecursiveTask> subtasks =
new ArrayList<MyRecursiveTask>();
MyRecursiveTask subtask1 = new MyRecursiveTask(this.workLoad / 2);
MyRecursiveTask subtask2 = new MyRecursiveTask(this.workLoad / 2);
subtasks.add(subtask1);
subtasks.add(subtask2);
return subtasks;
}
}
这个示例与RecursiveAction示例相似,不同的是它返回一个结果。MyRecursiveTask类扩展了RecursiveTask意味着任务返回的结果是一个Long类型。
MyRecursiveTask示例还将工作分解为子任务,并使用它们的fork()方法调度这些子任务的执行。
然后,这个示例通过调用每个子任务的join()方法接收每个子任务返回的结果,子任务结果合并为更大的结果,然后返回更大的结果。这种子任务结果的连接/合并可能会在多个级别的递归水平上递归地发生。
你可以这样调度一个RecursiveTask:
MyRecursiveTask myRecursiveTask = new MyRecursiveTask(128);
long mergedResult = forkJoinPool.invoke(myRecursiveTask);
System.out.println("mergedResult = " + mergedResult);
注意如何从ForkJoinPool.invoke()方法调用中获得最终结果。
ForkJoinPool批判
似乎不是每个人都对Java 7中的新的ForkJoinPool感到满意。在搜索关于ForkJoinPool的经验和观点时,我遇到了以下情况
- Java Fork-Join灾难
在您计划在自己的项目中使用ForkJoinPool之前,很值得一读。