深入学习JUC(八)——ForkJoinPool的使用
程序员文章站
2022-05-05 22:48:44
...
ForkJoinPool分支/合并框架
在必要的情况下,讲一个大任务,进行拆分(fork)成若干个小任务(拆到不可拆为止),再将一个个小的任务运算的结果进行join汇总。
工作窃取的背景
- 分支/合并框架,里面提到了ForkJoinSumCalculator会将一个任务分成很多个子任务,一般来说分出大量的子任务是个好的选择。因为在理想的情况下,划分并行任务时,应该要让每个任务都用完全相同的时间完成,让所有的CPU内核都同样繁忙。但是实际中,每个子任务所花费的时间可能天差地别,要么因为划分策略低,要么因为不可预知的原因,比如磁盘访问慢,或者需要和外部服务协调执行。
工作窃取的使用
- 针对多个线程分配相同多子任务的时候,会出现不同线程完成所有任务的时间有快有慢的情况,分支/合并框架工程使用了工作窃取的技术来解决这个问题。在实际应用中,这些子任务被差不多的分配到ForkJoinSumCalculator中的所有线程上,每个线程都为分配给他的任务保存一个双向的链式队列,每完成一个任务,就会队列头上取出下一个任务开始执行。因为上面所述的原因,有些线程可能早早地完成了分配给他的任务,也就是他的队列已经空了,但其他的线程还是很忙。这个时候队列已经空了的线程并不会闲置下来,而是随机选择一个其他的线程从队列的尾巴上“偷走”一个任务。这个过程会一直继续下去,知道所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务的原因,他能有助于工作线程之间的平衡负载。
demo
public class Thread {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> future = pool.submit(new Task(0, 100000));
future.get();
}
}
class Task extends RecursiveTask<Long> {
//定义划分任务的临界值
public final long THREAD_HOLD= 100;
private long start;
private long end;
Task(long start,long end){
this.start = start;
this.end = end;
}
public Long compute() {
if(end-start<THREAD_HOLD){
//如果小于定义的临界值 计算
long sum = 0;
for(long i = start;i < end;i++){
sum = sum + i;
}
return sum;
}else{
//拆分任务
long mid = (end-start)/2;
Task left = new Task(start, mid);
Task right = new Task(mid, end);
//fork提交任务到队列中
left.fork();
right.fork();
//执行任务
return left.join()+right.join();
}
}
}
推荐阅读
-
深入学习nodejs中的async模块的使用方法
-
【深入学习MySQL】MySQL的索引结构为什么使用B+树?
-
深入学习JavaScript的AngularJS框架中指令的使用方法
-
Kali的学习笔记篇(八)使用msf生成的shellcode制作免杀payload
-
Java开发桌面程序学习(八)——启动浏览器或者打开资源管理器操作与hyperlink超链接的使用
-
Angular 4依赖注入学习教程之InjectToken的使用(八)
-
Python学习笔记(八)—使用正则获取网页中所需要的信息。
-
Kotlin学习笔记八、Kotlin简单控件的使用
-
vue入门学习知识要点总结(八),Vue在脚手架的使用,过渡与动画,AJAX和插槽
-
深入学习nodejs中的async模块的使用方法