Fork/Join框架?由Doug Lea 提供的并行框架。貌似是无意中看到的,据说还要加到jdk7.0之中。
目前,有关Fork/Join的中文资源有限,而且各种版本是五花八门,好不容易找个例子吧,到处都是错误,万恶的版本问题!!
因此,本文有意统一一下版本:
Fork/Join框架下载:
从网站 http://gee.cs.oswego.edu/dl/concurrency-interest/ 下载Package jsr166y和Package extra166y;
其中,Package jsr166y是Java 7并行编程类的的初步版本(Preliminary versions of classes targeted for Java 7.);
Package extra166y是基于Package jsr166y之上的类的初步版本(Preliminary versions of classes that build upon those targeted for Java 7, but are not planned for inclusion in JDK. )
Fork/Join模式[1]
Fork/Join 模式有自己的适用范围。如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决。图 1 给出了一个 Fork/Join 模式的示意图,位于图上部的 Task 依赖于位于其下的 Task 的执行,只有当所有的子任务都完成之后,调用者才能获得 Task 0 的返回结果。
图 1. Fork/Join 模式示意图
可以说,Fork/Join 模式能够解决很多种类的并行问题。通过使用 Doug Lea 提供的 Fork/Join 框架,软件开发人员只需要关注任务的划分和中间结果的组合就能充分利用并行平台的优良性能。其他和并行相关的诸多难于处理的问题,例如负载平衡、同步等,都可以由框架采用统一的方式解决。这样,我们就能够轻松地获得并行的好处而避免了并行编程的困难且容易出错的缺点。
例子1 RecursiveAction 应用实例
在开始尝试 Fork/Join 模式之前,我们需要从 Doug Lea 主持的 Concurrency JSR-166 Interest Site 上下载 JSR-166y 的源代码,并且我们还需要安装最新版本的 JDK 6(下载网址请参阅 参考资源)。Fork/Join 模式的使用方式非常直观。首先,我们需要编写一个 ForkJoinTask 来完成子任务的分割、中间结果的合并等工作。随后,我们将这个 ForkJoinTask 交给 ForkJoinPool 来完成应用的执行。
通常我们并不直接继承 ForkJoinTask,它包含了太多的抽象方法。针对特定的问题,我们可以选择 ForkJoinTask 的不同子类来完成任务。RecursiveAction 是 ForkJoinTask 的一个子类,它代表了一类最简单的 ForkJoinTask:不需要返回值,当子任务都执行完毕之后,不需要进行中间结果的组合。如果我们从 RecursiveAction 开始继承,那么我们只需要重载 protected void compute() 方法。下面,我们来看看怎么为快速排序算法建立一个 ForkJoinTask 的子类:
package fj;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import jsr166y.ForkJoinPool;
import jsr166y.ForkJoinTask;
import jsr166y.RecursiveAction;
class SortTask extends RecursiveAction {
final long[] array;
final int lo;
final int hi;
private int THRESHOLD = 2000; //For demo only
public SortTask(long[] array) {
this.array = array;
this.lo = 0;
this.hi = array.length - 1;
}
public SortTask(long[] array, int lo, int hi) {
this.array = array;
this.lo = lo;
this.hi = hi;
}
protected void compute() {
if (hi - lo < THRESHOLD) {
sequentiallySort(array, lo, hi);
System.out.println("array,NO \n" + Arrays.toString(array));
} else {
int pivot = partition(array, lo, hi);
System.out.println("\npivot = " + pivot + ", low = " + lo + ", high = " + hi);
System.out.println("array" + Arrays.toString(array));
invokeAll(new SortTask(array, lo, pivot - 1), new SortTask(array, pivot + 1, hi));
}
}
private int partition(long[] array, int lo, int hi) {
long x = array[hi];
int i = lo - 1;
for (int j = lo; j < hi; j++) {
if (array[j] <= x) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, hi);
return i + 1;
}
private void swap(long[] array, int i, int j) {
if (i != j) {
long temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
private void sequentiallySort(long[] array, int lo, int hi) {
Arrays.sort(array, lo, hi + 1);//Only one question!
}
public static void main(String... args) throws InterruptedException {
long[] array = new long[15000];
Random rand = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = rand.nextLong() % 100; //For demo only
}
ForkJoinTask sort = new SortTask(array);
ForkJoinPool fjpool = new ForkJoinPool();
fjpool.execute(sort);
fjpool.shutdown();
fjpool.awaitTermination(30, TimeUnit.SECONDS);
}
}
解释:
SortTask 首先通过 partition()
方法将数组分成两个部分。随后,两个子任务将被生成并分别排序数组的两个部分。当子任务足够小时,再将其分割为更小的任务反而引起性能的降低。因此,这里我们使用一个 THRESHOLD
,限定在子任务规模较小时,使用直接排序,而不是再将其分割成为更小的任务。
RecursiveAction 提供的方法 invokeAll()
。它表示:启动所有的任务,并在所有任务都正常结束后返回。如果其中一个任务出现异常,则其它所有的任务都取消。invokeAll()
的参数还可以是任务的数组。
execute()
:将 ForkJoinTask 类的对象提交给 ForkJoinPool,ForkJoinPool 将立刻开始执行 ForkJoinTask。
shutdown()
:执行此方法之后,ForkJoinPool 不再接受新的任务,但是已经提交的任务可以继续执行。如果希望立刻停止所有的任务,可以尝试 shutdownNow()
方法。
awaitTermination()
:阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束。
参考文献:
[1]甘志,戴晓君.JDK 7 中的 Fork/Join 模式.http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/index.html 。