1.什么是Fork/Join框架?
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
2.Fork/Join框架的结构:需要ForkJoinPool才能使用
在Fork/Join中,我们主要用它自定义的线程池来提交任务和调度任务,称之为:ForkJoinPool;同时我们有它自己的任务执行类,称之为:ForkJoinTask。通常情况下,我们不需要直接集成ForkJoinTask类,只需要继承它的子类。我们一般都使用它的两个子类,RecursiveAction和RecursiveTask,其中,前者主要处理没有返回结果的任务,后者主要处理有返回结果的任务。
3.Fork/Join框架的“工作窃取”模式
什么是“工作窃取”呢?简单来说,就是说你和你的一个伙伴一起吃零食,你的那份吃完了,他那份没吃完,那你就偷偷的拿了他的一些水果吃了。
原先的线程池,cpu给线程池中的线程分配任务,形成一个单端的任务队列,任务只能按一个顺序从队列中被领取,然后执行,这就可能导致,当有一个任务因为某些原因被阻塞的时候,队列中剩下的任务无法被执行。这就可能存在这样一个现象:线程池中一部分的线程完成了任务队列中的所有任务处于空闲状态,而有一部分线程因为任务阻塞无法继续。
而Fork/Join提供的“工作窃取”模式可以有效解决上述的问题,任务队列是双端的,也就是说,其他线程在执行完自己队列中的任务后,可以从其他线程的任务队列的尾端获取任务来执行,从而提高cpu的利用效率。
举个例子:计算0到500000000的结果
public class TestForkJoinPool {
public static void main(String[] args){
Instant start = Instant.now();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinSum forkJoinSum = new ForkJoinSum(0L,500000000L);
Long sum = forkJoinPool.invoke(forkJoinSum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:"+ Duration.between(start,end).toMillis());
}
}
class ForkJoinSum extends RecursiveTask<Long>{
private long start;
private long end;
private static final long THURSHOLD = 10000L;//设置临界值
public ForkJoinSum(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THURSHOLD){
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinSum left = new ForkJoinSum(start, middle);
left.fork(); //进行拆分,同时压入线程队列
ForkJoinSum right = new ForkJoinSum(middle+1, end);
right.fork();
return left.join() + right.join();
}
}
}
结果:
如果把临界值改成50000L的结果:
可以看出,不同的临界值,耗费的时间也是不同的,也就是说,有这么一个临界值,可以让耗费的时间达到最小值。而这个临界值就可以看做是任务的大小,在进入computer方法的时候,首先会判断,当前的任务是否足够小,如果足够小就执行任务;如果不是足够小,那就分割任务,就又会进入computer方法,看当前的任务是否需要继续分割子任务,以一种递归的方式进行分割。使用join方法会等待子任务执行完并得到其结果。
@Test
public void test1(){
Instant start = Instant.now();
Long sum = 0L;
for (long i = 0; i <= 500000000L ; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间:"+Duration.between(start,end).toMillis());
}
结果:
跟java8 写法的对比:
@Test
public void test2(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0L,500000000L).parallel().reduce(0L,Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间:"+Duration.between(start,end).toMillis());
}
结果:
