1.Fork、Join
1.1概述
1.1 简介
- Fork/Join框架是用于并行执行任务的框架,一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
- Fork/Join Pool采用优良的设计、代码实现和硬件原子操作机制等多种思路保证其执行性能
- 多线程的目的不仅仅是提高程序运行的性能,但是可以充分利用CPU资源
1.1.2 原理
-
第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。
-
第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
-
Fork/Join使用两个类来完成以上两件事情:
-
- ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
-
- RecursiveAction:用于没有返回结果的任务。
-
- RecursiveTask :用于有返回结果的任务。
-
- ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
1.2 示例
1.2.1
用Fork/Join框架计算1+2+3+4的结果。
使用Fork/Join框架首先要考虑到的是如何分割任务,如果我们希望每个子任务最多执行两个数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以Fork/Join框架会把这个任务fork成两个子任务,子任务一负责计算1+2,子任务二负责计算3+4,然后再join两个子任务的结果。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask {
private static final int THRESHOLD= 2;//阈值
private int start;
private int end;
public CountTask(int start,int end) {
this.start= start;
this.end= end;
}
@Override
protected Integer compute() {
int sum = 0;
//如果任务足够小就计算任务
boolean canCompute = (end-start) <=THRESHOLD;
if(canCompute) {
for(int i =start; i <=end; i++) {
sum += i;
}
}else{
//如果任务大于阀值,就分裂成两个子任务计算
int middle = (start+end) / 2;
CountTask leftTask =new CountTask(start, middle);
CountTask rightTask =new CountTask(middle + 1,end);
//执行子任务
leftTask.fork();
rightTask.fork();
//等待子任务执行完,并得到其结果
int leftResult=(int)leftTask.join();
int rightResult=(int)rightTask.join();
//合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool =new ForkJoinPool();
//生成一个计算任务,负责计算1+2+3+4
CountTask task =new CountTask(1, 4);
//执行一个任务
Future result = forkJoinPool.submit(task);
try{
System.out.println(result.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
- 通过这个例子让我们再来进一步了解ForkJoinTask,ForkJoinTask与一般的任务的主要区别在于它需要实现compute方法,在这个方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用fork方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。
1.2.2
- ForkJoinThread
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class ForkJoinThread extends RecursiveTask<Integer> {
private int begin;
private int end;
public ForkJoinThread(int begin, int end) {
this.begin = begin;
this.end = end;
}
private static int i=0;
@Override
protected Integer compute() {
System.out.println(Thread.currentThread().getName() + " ... "+i);
i++;
int sum = 0;
// 拆分任务,差值小于等于2的直接计算(如:1~3)
if (end - begin <= 2) {
// 计算
for (int i = begin; i <= end; i++) {
sum += i;
}
} else {
// 拆分
//如果 1~50=拆分=>1~25[1,(1+50)/2=25]和26~50[26~50 ]
//如果1~49=拆分==>1~25[1,(1+49)/2=25]和26~49
ForkJoinThread forkJoinThread1 = new ForkJoinThread(begin, (begin + end) / 2);//会自动调用ForkJoinThread的Compute方法
ForkJoinThread forkJoinThread2 = new ForkJoinThread((begin + end) / 2 + 1, end);
// 执行任务
forkJoinThread1.fork();
forkJoinThread2.fork();
Integer a = forkJoinThread1.join();
Integer b = forkJoinThread2.join();
sum = a + b;
}
return sum;
}
/**
* ForkJoinPool-1-worker-1 ... 0
ForkJoinPool-1-worker-1 ... 1
ForkJoinPool-1-worker-1 ... 2
ForkJoinPool-1-worker-1 ... 3
ForkJoinPool-1-worker-1 ... 4
ForkJoinPool-1-worker-1 ... 5
ForkJoinPool-1-worker-1 ... 6
ForkJoinPool-1-worker-1 ... 7
ForkJoinPool-1-worker-1 ... 8
ForkJoinPool-1-worker-1 ... 9
ForkJoinPool-1-worker-1 ... 10
ForkJoinPool-1-worker-1 ... 11
ForkJoinPool-1-worker-1 ... 12
ForkJoinPool-1-worker-1 ... 13
ForkJoinPool-1-worker-1 ... 14
ForkJoinPool-1-worker-1 ... 15
ForkJoinPool-1-worker-1 ... 16
ForkJoinPool-1-worker-1 ... 17
ForkJoinPool-1-worker-1 ... 18
ForkJoinPool-1-worker-1 ... 19
ForkJoinPool-1-worker-3 ... 19
ForkJoinPool-1-worker-2 ... 20
ForkJoinPool-1-worker-3 ... 21
ForkJoinPool-1-worker-2 ... 22
ForkJoinPool-1-worker-3 ... 23
ForkJoinPool-1-worker-2 ... 24
ForkJoinPool-1-worker-3 ... 25
ForkJoinPool-1-worker-2 ... 26
ForkJoinPool-1-worker-3 ... 27
ForkJoinPool-1-worker-2 ... 28
ForkJoinPool-1-worker-3 ... 29
ForkJoinPool-1-worker-0 ... 30
ForkJoinPool-1-worker-2 ... 30
ForkJoinPool-1-worker-0 ... 32
ForkJoinPool-1-worker-3 ... 31
ForkJoinPool-1-worker-2 ... 33
ForkJoinPool-1-worker-3 ... 35
ForkJoinPool-1-worker-2 ... 36
ForkJoinPool-1-worker-0 ... 36
ForkJoinPool-1-worker-2 ... 38
ForkJoinPool-1-worker-3 ... 37
ForkJoinPool-1-worker-2 ... 40
ForkJoinPool-1-worker-0 ... 39
ForkJoinPool-1-worker-2 ... 42
ForkJoinPool-1-worker-3 ... 41
ForkJoinPool-1-worker-2 ... 44
ForkJoinPool-1-worker-0 ... 43
ForkJoinPool-1-worker-2 ... 46
ForkJoinPool-1-worker-3 ... 45
ForkJoinPool-1-worker-2 ... 48
ForkJoinPool-1-worker-0 ... 47
ForkJoinPool-1-worker-2 ... 50
ForkJoinPool-1-worker-3 ... 49
ForkJoinPool-1-worker-0 ... 52
ForkJoinPool-1-worker-2 ... 52
ForkJoinPool-1-worker-0 ... 54
ForkJoinPool-1-worker-3 ... 53
ForkJoinPool-1-worker-0 ... 56
ForkJoinPool-1-worker-2 ... 55
ForkJoinPool-1-worker-0 ... 58
ForkJoinPool-1-worker-1 ... 58
ForkJoinPool-1-worker-3 ... 57
ForkJoinPool-1-worker-1 ... 61
ForkJoinPool-1-worker-0 ... 60
ForkJoinPool-1-worker-1 ... 63
ForkJoinPool-1-worker-3 ... 62
ForkJoinPool-1-worker-0 ... 64
ForkJoinPool-1-worker-3 ... 66
ForkJoinPool-1-worker-0 ... 67
ForkJoinPool-1-worker-3 ... 68
ForkJoinPool-1-worker-0 ... 69
计算的值为:5050
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool(3); //启用3个线程.
Future<Integer> future = pool.submit(new ForkJoinThread(1, 100));//会调用fork,join的compute方法
System.out.println("计算的值为:" + future.get());
}
}
1.3 常用API
- RecursiveTask
- compute()
1.4 参考文章
好—聊聊并发(八)——Fork/Join框架介绍
Fork/Join框架原理解析
Fork/Join框架基本使用
Java–8--新特性–串并行流与ForkJoin框架
JAVA并行框架学习之ForkJoin
— Fork、Join—