Fork/Join框架的思想
Fork/Join的思想是一个分而治之的思想的实现,跟MapReduce的思想如出一辙,简单的说就是加入我们需要处理1000个数据,但是我们并不具备处理1000个数据的能力,那么我们可以让一个线程只处理其中的10个,然后分阶段处理100次,将100次的结果进行合并,那么就会得到对最终的1000个数据的处理结果
JDK并发包中的实现
在实际应用中,我们没有办法毫无顾忌地使用fork()开启线程进行处理,假使我们这么做了,那么很有可能导致系统开启过多的线程而严重影响性能。所以在JDK中,给出了一个ForkJoinPool的线程池,对于fork()方法并不急着开启线程,而是提交给ForkJoinPool线程池进行处理,以节省系统资源。
ForkJoinPool的submit方法:
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)
这个方法需要一个ForkJoinTask类型的参数,ForkJoinTask就是支持fork()分解和join()等待的任务。ForkJoinTask是一个抽象类,它有两个子类,他们分别表示没有返回值的任务和有返回值的任务
RecursiveAction
RecursiveTask<V>
图解Fork/Join框架:
案例
计算若干个数值的和
public class CountTask extends RecursiveTask<Long>{
//每次相加的范围是1000个数
private static final Long THRESHOLD = 10L;
private long start;
private long end;
public CountTask(long start, long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
boolean canCompute = (end-start)<THRESHOLD;
if(canCompute){
System.out.println(start+"\t"+end);
for(long i =start;i<=end;i++){
sum+=i;
}
}else{
//分成若干个小任务
long step = (end-start+1)/10;
List<CountTask> subTasks = new ArrayList<>();
for(int i=0;i<Math.ceil((end-start+1)/10.0);i++){
CountTask countTask = null;
if((start+step*(i+1)-1)>end){
countTask = new CountTask(start+step*i, end);
}else{
countTask = new CountTask(start+step*i, start+step*(i+1)-1);
}
subTasks.add(countTask);
countTask.fork();
}
for(CountTask c:subTasks){
sum+=c.join();
}
}
return sum;
}
public static void main(String[] args) throws Exception {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask countTask = new CountTask(1, 101);
ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(countTask);
System.out.println(forkJoinTask.get());
}
}
新建一个可以fork()/join()的任务,由于是计算总和,所以这里我们需要有返回值的任务,这里就是继承RecursiveTask
重写compute方法,在这个里面对任务进行分解
针对每一个分解的任务,进行fork操作
针对每一个被分解的任务进行join操作,等待任务执行完毕后的数据的汇总
注意
由于我们这里边使用的是递归的方式,所以我们需要注意不能将任务划分的过深,否则会造成下面的问题:
* 系统内的线程越积越多,导致性能的严重下降
* 函数的调用层次(递归的层次)过多,导致栈溢出