ForkJoinTask这个名字,第一次听到,立马让我想起了hadoop里的mapreduce编程模型。
处理数据时主要分为两个阶段:1.map(Fork)阶段,单程序(线程)处理计算阶段 2.reduce(join)阶段,计算结果汇总阶段
处于好奇,我还是决定自己来写一个简单的ForkJoinTask的例子,体验下
例子的功能描述:
计算1+2+3+……1000000000的和,即1000000000!的结果
先写一个不采用ForkJoinTask的实现方式,以此做对比
/**
* desc:计算1+2+3+……1000000000的和
* author: haiyangp
* date: 2017/8/25
*/
public class NotForkJoinTask {
public static void main(String[] args) {
new Thread(()->{
Long sum = 0l;
Long maxSize = 1000000000l;
long startTime = System.currentTimeMillis();
for (Long i = 1l; i <= maxSize; i++) {
sum += i;
}
System.out.println("结果--》result:"+sum);
long endTime = System.currentTimeMillis();
System.out.println("costTime:"+(endTime-startTime));
}).start();
}
}
运行结果为:
结果--》result:500000000500000000
costTime:10121
采用ForkJoinTask的代码为:
/**
* desc: 计算task(计算1+2+3+……1000000000的和)
* author: haiyangp
* date: 2017/8/25
*/
public class CountTask extends RecursiveTask<Long> {
Long maxCountRange = 100000000l;//最大计算范围
Long startNum, endNum;
public CountTask(Long startNum, Long endNum) {
this.startNum = startNum;
this.endNum = endNum;
}
@Override
protected Long compute() {
long range = endNum - startNum;
long sum = 0;
if (range >= maxCountRange) {//如果这次计算的范围大于了计算时规定的最大范围,则进行拆分
//每次拆分时,都拆分成原来任务范围的一半
//如1-10,则拆分为1-5,6-10
Long middle = (startNum + endNum) / 2;
CountTask subTask1 = new CountTask(startNum, middle);
CountTask subTask2 = new CountTask(middle + 1, endNum);
//拆分后,执行fork
subTask1.fork();
subTask2.fork();
sum += subTask2.join();
sum += subTask1.join();
} else {//在范围内,则进行计算
for (; startNum <= endNum; startNum++) {
sum += startNum;
}
}
return sum;
}
public static void main(String[] args) {
Long startNum = 1l;
Long endNum = 1000000000l;
long startTime = System.currentTimeMillis();
CountTask countTask = new CountTask(startNum, endNum);
ForkJoinPool forkJoinPool = new ForkJoinPool();
Future<Long> result = forkJoinPool.submit(countTask);
try {
System.out.println("结果--》result:" + result.get());
long endTime = System.currentTimeMillis();
System.out.println("costTime:" + (endTime - startTime));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
运行结果为:
结果--》result:500000000500000000
costTime:4081
对于为什么ForkJoinTask比单线程的快呢?我看了一些文章后,才知道原来是主要因为ForkJoinPool这个线程池下有一个双端队列,以及线程获取任务时采用的Work Stealing模式来获取的。
在ForkJoinPool的源码中我们也能看到相应的代码与说明:
/** * Queues supporting work-stealing as well as external task * submission. See above for descriptions and algorithms. * Performance on most platforms is very sensitive to placement of * instances of both WorkQueues and their arrays -- we absolutely * do not want multiple WorkQueue instances or multiple queue * arrays sharing cache lines. The @Contended annotation alerts * JVMs to try to keep instances apart. */ @sun.misc.Contended static final class WorkQueue {
Queues supporting work-stealing as well as external task submission.(队列支持工作窃取以及外部的任务提交)
对于ForkJoinTask更多的说明,在https://yq.aliyun.com/articles/48739这个链接下的3篇文章中有较为详细的说明