RecursiveAction继承自ForkJoinTask,代表一个异步执行的结果,是没有返回值的任务。
而RecursiveTask是有返回值的任务,这两个共同组成ForkJoin框架的任务。
一个简单的例子
以下是java API中给出的RecursiveAction的例子,将给定数组的每个元素都自增:
public class IncrementTask extends RecursiveAction {
public static final double THRESHOLD = 4;
final long[] array; final int lo, hi;
IncrementTask(long[] array, int lo, int hi) {
this.array = array; this.lo = lo; this.hi = hi;
}
protected void compute() {
if (hi - lo < THRESHOLD) {
for (int i = lo; i < hi; ++i)
array[i]++;
} else {
int mid = (lo + hi) >>> 1;
invokeAll(new IncrementTask(array, lo, mid),
new IncrementTask(array, mid, hi));
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
long[] array = new long[]{11,1,4,5,6,7,9};
pool.invoke(new IncrementTask(array,0,array.length-1));
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
其中重写的compute方法中的invokeAll()方法中执行并行的逻辑,这是使用分治的思想。
但这个方法并没有涉及fork() 和 join()方法,所以不太典型。
体现Fork/Join的例子
double sumOfSquares(ForkJoinPool pool, double[] array) {
int n = array.length;
Applyer a = new Applyer(array, 0, n, null);
pool.invoke(a);
return a.result;
}
class Applyer extends RecursiveAction {
final double[] array;
final int lo, hi;
double result;
Applyer next; // keeps track of right-hand-side tasks
Applyer(double[] array, int lo, int hi, Applyer next) {
this.array = array; this.lo = lo; this.hi = hi;
this.next = next;
}
double atLeaf(int l, int h) {
double sum = 0;
for (int i = l; i < h; ++i) // perform leftmost base step
sum += array[i] * array[i];
return sum;
}
protected void compute() {
int l = lo;
int h = hi;
Applyer right = null;
while (h - l > 1 && getSurplusQueuedTaskCount() <= 3) {
int mid = (l + h) >>> 1;
right = new Applyer(array, mid, h, right);
right.fork();
h = mid;
}
double sum = atLeaf(l, h);
while (right != null) {
if (right.tryUnfork()) // directly calculate if not stolen
sum += right.atLeaf(right.lo, right.hi);
else {
right.join();
sum += right.result;
}
right = right.next;
}
result = sum;
}
}
这个例子是求给定数组元素的平方和,深入compute方法中可以看到先是将数组利用分治的方法进行分割执行,然后从叶子节点进行计算,最后通过一个循环累加所有右侧片段的平方和。
从这个例子可以看出RecursiveAction不是必须完全递归的,只要我们使用分治的思想就可以充分的利用fork/joinu框架来加速计算过程,通常这样的方式比递归更加高效,因为递归会产生很多重复的计算。
例如计算斐波那契数的递归算法就包含了很多重复的计算过程,反而for循环迭代的计算方式效果更好。