1. 什么是Fork/Join框架
Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。Fork/Join的运行流程图如下:
我们已经很清楚Fork/Join框架的需求了,那么我们可以思考一下,如果让我们来设计一个Fork/Join框架,该如何设计?这个思考有助于你理解Fork/Join框架的设计。
第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。
第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
java.util.concurrent
类 ForkJoinTask
Fork/Join使用两个类来完成以上两件事情:
让我们通过一个简单的需求来使用下Fork/Join框架,需求是:计算1+2+3+4的结果。
使用Fork/Join框架首先要考虑到的是如何分割任务,如果我们希望每个子任务最多执行两个数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以Fork/Join框架会把这个任务fork成两个子任务,子任务一负责计算1+2,子任务二负责计算3+4,然后再join两个子任务的结果。因为是有结果的任务,所以必须继承RecursiveTask,实现代码如下:
package Concurrent;
import java.util.concurrent.RecursiveTask;
/**
*
* @author zhengchao
*/
public class CalculatorFork_Join extends RecursiveTask {
private static final int THRESHOLD = 10000;
private int start;
private int end;
public CalculatorFork_Join(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
if((end - start) < THRESHOLD){
for(int i = start; i<= end;i++){
sum += i;
}
}else{
int middle = (start + end) /2;
CalculatorFork_Join left = new CalculatorFork_Join(start, middle);
CalculatorFork_Join right = new CalculatorFork_Join(middle + 1, end);
left.fork();
right.fork();
sum = left.join() + right.join();
}
return sum;
}
}
package Concurrent;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
/**
*
* @author zhengchao
*/
public class testMain {
public static void main(String[] args) throws ExecutionException, InterruptedException{
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
Future result = forkJoinPool.submit(new CalculatorFork_Join(0, 1000000));
System.out.println("ForkJoinCostTime:"+(System.currentTimeMillis()-start));
System.out.println(result.get());
start = System.currentTimeMillis();
long sum = 0;
for(int i=0;i<=1000000;i++){
sum+=i;
}
System.out.println("CostTime:"+(System.currentTimeMillis()-start));
System.out.println(sum);
forkJoinPool.shutdown();
}
}
通过这个例子让我们再来进一步了解ForkJoinTask,ForkJoinTask与一般的任务的主要区别在于它需要实现compute方法,在这个方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用fork方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。
性能分析:
1、计算1-10000
run:
ForkJoinCostTime:1
50005000
CostTime:0
50005000
成功构建 (总时间: 0 秒)
数据量较小时,是用Fork/Join效果不明显,甚至不如正常的计算,这是因为创建 Fork/Join耗时了。
run:
ForkJoinCostTime:1
5000050000
CostTime:2
5000050000
成功构建 (总时间: 0 秒)
3、计算1-1000000
run:
ForkJoinCostTime:1
500000500000
CostTime:3
500000500000
成功构建 (总时间: 0 秒)
private static final int THRESHOLD = 10000;
设置为100,运行计算 计算1-1000000:
run:
ForkJoinCostTime:2
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError
at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:942)
at Concurrent.testMain.main(testMain.java:26)
Caused by: java.lang.OutOfMemoryError
at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:536)
at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:941)
... 1 more
Caused by: java.lang.OutOfMemoryError
at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:536)
at java.util.concurrent.ForkJoinTask.reportResult(ForkJoinTask.java:596)
at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:640)
at Concurrent.CalculatorFork_Join.compute(CalculatorFork_Join.java:40)
at Concurrent.CalculatorFork_Join.compute(CalculatorFork_Join.java:15)
at java.util.concurrent.RecursiveTask.exec(RecursiveTask.java:93)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:334)
at java.util.concurrent.ForkJoinWorkerThread.execTask(ForkJoinWorkerThread.java:604)
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:784)
at java.util.concurrent.ForkJoinPool.work(ForkJoinPool.java:646)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:398)
Caused by: java.lang.OutOfMemoryError
at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:536)
at java.util.concurrent.ForkJoinTask.reportResult(ForkJoinTask.java:596)
at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:640)
at Concurrent.CalculatorFork_Join.compute(CalculatorFork_Join.java:40)
at Concurrent.CalculatorFork_Join.compute(CalculatorFork_Join.java:15)
at java.util.concurrent.RecursiveTask.exec(RecursiveTask.java:93)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:334)
at java.util.concurrent.ForkJoinWorkerThread.execTask(ForkJoinWorkerThread.java:604)
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:762)
... 2 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError
... 14 more
Caused by: java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:713)
at java.util.concurrent.ForkJoinPool.addWorker(ForkJoinPool.java:1132)
at java.util.concurrent.ForkJoinPool.tryPreBlock(ForkJoinPool.java:1009)
at java.util.concurrent.ForkJoinPool.tryAwaitJoin(ForkJoinPool.java:1042)
at java.util.concurrent.ForkJoinWorkerThread.joinTask(ForkJoinWorkerThread.java:731)
at java.util.concurrent.ForkJoinTask.doJoin(ForkJoinTask.java:362)
at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:639)
... 8 more
Java Result: 1
成功构建 (总时间: 0 秒)
内存溢出了,为什么呢?因为我们创建了太大的线程,导致内存消耗殆尽。
所以,阈值要设定的恰当,否则可能效率并不会提高,设置导致异常的产生。