Fork-Join 框架
概述
Fork\Join 框架是 JDK1.7 加入的一种Java并发编程模型,它体现的是一种分治思想, 它是基于工作窃取(work-stealing)算法的一种并发框架,适用于能够进行任务拆分的 cpu 密集型运算。
所谓任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。例如:和递归相关的一些计算,如归并排序、斐波那契数列等都可以用分治思想进行求解。
Fork-Join 框架的核心就是将大任务拆解成多个小任务,然后将这些小任务分配给不同的线程进行并行执行。当一个线程执行完自己的任务后,它可以从其他线程的任务队列中"偷取"一个任务来执行,以避免线程出现闲置等待的情况。
Fork/Join 框架的主要组件是 ForkJoinPool
、ForkJoinTask
和 RecursiveTask
(用于有返回值的任务)或 RecursiveAction
(用于无返回值的任务)。
使用Fork-Join
提交给 Fork-Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),
案例1:对 1~n 之间的整数求和
java
@Slf4j
public class ForkJoinDemo {
public static void main(String[] args) {
ForkJoinPool p = new ForkJoinPool(8);//指定线程池的工作线程数,如果不指定,默认工作线程数和CPU核数一样
System.out.println(p.invoke(new MyTask(10)));//使用invoke提交并获取到结果
}
}
@Data
@Slf4j
class MyTask extends RecursiveTask<Integer> {
private int n;
public MyTask(int n){
this.n =n;
}
@Override
public String toString() {
return "{" + n + '}';
}
/**
* The main computation performed by this task.
*
* @return the result of the computation
*/
@Override
protected Integer compute() {
System.out.println("开始进行从1 "+ " 到 "+n+" 累加任务");
if(n==1){
log.debug("join() {}", n);
return n;
}
MyTask t = new MyTask(n-1);
t.fork();
log.debug("fork() {} + {}", n, t);
return n+t.join();
}
}
输出结果:
css
开始进行从1 到 10 累加任务
开始进行从1 到 9 累加任务
开始进行从1 到 8 累加任务
开始进行从1 到 7 累加任务
开始进行从1 到 6 累加任务
开始进行从1 到 5 累加任务
开始进行从1 到 4 累加任务
开始进行从1 到 3 累加任务
10:41:55.500 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.MyTask - fork() 9 + {8}
10:41:55.500 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.MyTask - fork() 8 + {7}
10:41:55.500 [ForkJoinPool-1-worker-0] DEBUG com.avgrado.demo.thread.MyTask - fork() 3 + {2}
10:41:55.500 [ForkJoinPool-1-worker-5] DEBUG com.avgrado.demo.thread.MyTask - fork() 6 + {5}
开始进行从1 到 2 累加任务
10:41:55.500 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.MyTask - fork() 4 + {3}
10:41:55.500 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.MyTask - fork() 7 + {6}
开始进行从1 到 1 累加任务
10:41:55.500 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.MyTask - fork() 5 + {4}
10:41:55.513 [ForkJoinPool-1-worker-0] DEBUG com.avgrado.demo.thread.MyTask - fork() 2 + {1}
10:41:55.500 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.MyTask - fork() 10 + {9}
10:41:55.513 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.MyTask - join() 1
55
案例2:对 1~1000之间的整数求和,每100切分一次,最后叠加求结果
java
@Slf4j
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CacluTask task = new CacluTask(1,1000);
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> data = pool.submit(task);//使用submit提交
System.out.println(data.get());
}
}
@Data
@Slf4j
class CacluTask extends RecursiveTask<Long> {
private int start;
private int end;
private long result;
public CacluTask(int start,int end){
this.start = start;
this.end = end;
}
/**
* The main computation performed by this task.
*
* @return the result of the computation
*/
@Override
protected Long compute() {
//完成1+1000,每100累加
if(end -start <=100){
log.debug("开始进行累加从 "+start+" 到 "+end+" 累加任务");
for(int i = start;i<=end;i++){
result +=i;
}
log.debug("join() {}",result);
}else {
int middle = start + 100;
//递归调用
CacluTask task1 = new CacluTask(start,middle-1);
CacluTask task2 = new CacluTask(middle,end);
task1.fork();
log.debug("fork() {} {} {} {}",start,middle,end,task1);
task2.fork();
log.debug("fork() {} {} {} {}",start,middle,end,task2);
//同步执行结果
result = task1.join() + task2.join();
}
return result;
}
}
计算结果:
ini
10:50:22.025 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - fork() 1 101 1000 CacluTask(start=1, end=100, result=0)
10:50:22.025 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 1 到 100 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - join() 5050
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - fork() 1 101 1000 CacluTask(start=101, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 101 到 200 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 101 201 1000 CacluTask(start=101, end=200, result=15050)
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - join() 15050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 101 201 1000 CacluTask(start=201, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 201 301 1000 CacluTask(start=201, end=300, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 201 到 300 累加任务
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join() 25050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 301 到 400 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 201 301 1000 CacluTask(start=301, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join() 35050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 301 401 1000 CacluTask(start=301, end=400, result=35050)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 401 到 500 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 301 401 1000 CacluTask(start=401, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join() 45050
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 401 501 1000 CacluTask(start=401, end=500, result=45050)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 401 501 1000 CacluTask(start=501, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 501 601 1000 CacluTask(start=501, end=600, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 501 到 600 累加任务
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 601 到 700 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 601 701 1000 CacluTask(start=601, end=700, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - join() 55050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join() 65050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 501 601 1000 CacluTask(start=601, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 701 到 800 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 601 701 1000 CacluTask(start=701, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - fork() 701 801 1000 CacluTask(start=701, end=800, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - join() 75050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 801 到 900 累加任务
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - fork() 701 801 1000 CacluTask(start=801, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 801 901 1000 CacluTask(start=801, end=900, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join() 85050
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 901 到 1000 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 801 901 1000 CacluTask(start=901, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - join() 95050
500500
从上述的两个案例中可以看到ForkJoinPool提交任务的方式有两种:
- submit:可提交 Callable 、ForkJoinTask、Runnable 类型的任务
- invoke:可提交 ForkJoinTask 类型的任务 代码中提交给 Fork-Join 线程池的任务类继承的 RecursiveTask 是 ForkJoinTask的子类, RecursiveAction 也是ForkJoinTask的子类
需要注意的点
Fork-Join 框架适合处理一些比较大型的任务,如果是小量级别的任务,Fork-Join的处理效率就显得并不高效了。这是因为Fork-Join框架在拆分任务和合并结果都需要时间,而且开启多个线程处理任务时也涉及到CPU的切换耗时。这种情况下就不适合使用 Fork-Join框架