java并发编程(十八)----(线程池)java线程池框架Fork-Join
还记得我们在初始介绍线程池的时候提到了Executor框架的体系,到现在为止我们只有一个没有介绍,与ThreadPoolExecutor一样继承与AbstractExecutorService的ForkJoinPool.Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
我们通过表面的意思去理解ForkJoin框架:Fork即把一个大任务切割成若干部分并行执行,join即把这些被切分的任务的执行结果合并一起汇总,我们可以用下图来表示:

Fork / Join的逻辑很简单:
(1)将每个大任务分离(fork)为较小的任务;
(2)在单独的线程中处理每个任务(如果必要,将它们分离成更小的任务);
(3)加入结果。
Fork/Join框架的核心是由下列两个类组成的。
①ForkJoinPool:这个类实现了ExecutorService接口和工作窃取算法(Work-Stealing Algorithm)。它管理工作者线程,并提供任务的状态信息,以及任务的执行信息。
②ForkJoinTask:这个类是一个将在ForkJoinPool中执行的任务的基类。
理解一个概念的最好方法是在实践中体会他,我们先写一个小程序,在此基础上一点一点来分析:
public class ForkJoinPoolTest {
public static void main(String[] args) throws InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
pool.submit(new PrintTask(1,100));
pool.awaitTermination(2, TimeUnit.SECONDS);//阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
pool.shutdown();
}
}
class PrintTask extends RecursiveAction{
private int start;
private int end;
private int num;
final int MAX = 50;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if(end - start < 50){
for(int i = start;i <= end; i++){
num += i;
}
System.out.println("当前任务结果为: "+num);
}else{
int mid = (end + start)/2;
PrintTask left = new PrintTask(start,mid);
PrintTask right = new PrintTask(mid+1,end);
left.fork();
right.fork();
}
}
}
结果为:
当前任务结果为: 3775
当前任务结果为: 1275
Process finished with exit code 0
我们通过结果可以看到当前任务被分裂为两个子任务去执行。而执行任务的类继承了RecursiveAction这个类,那他到底在Fork-Join框架中发挥什么作用呢?我们不妨看一下:
首先我们来看一下Fork-Join框架提交任务的方法仍旧还是submit和execute:
void execute(ForkJoinTask<?> task) //安排(异步)执行给定任务
void execute(Runnable task) //在未来的某个时候执行给定的命令
<T> ForkJoinTask<T> submit(Callable<T> task) //执行一个有返回值得任务,返回一个Future类型的实例代表任务的结果
<T> ForkJoinTask<T> submit(ForkJoinTask<T> task) //提交一个ForkJoinTask类型的任务
ForkJoinTask<?> submit(Runnable task) //提交一个Runnable类型的任务,返回一个Future类型的实例代表任务结果
<T> ForkJoinTask<T> submit(Runnable task, T result) //提交一个Runnable类型的任务,返回一个Future类型的实例代表任务结果
由execute和submit的参数我们可以看到Fork-join框架可以提交ForkJoinTask,Callable和Runnable类型的任务。这个ForkJoinTask我们之前没见过,先来看一下:
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
}
我们看到ForkJoinTask实现了Future接口,一个ForkJoinTask是一个轻量级的Future。对ForkJoinTask效率源于一组限制(这只是部分静态强制执行)反映其用途作为计算任务计算纯函数或纯粹孤立的对象操作。主要的协调机制fork(),安排异步执行,而不进行join(),直到任务的结果已经计算。通常我们并不直接继承 ForkJoinTask,它包含了太多的抽象方法。针对特定的问题,我们可以选择 ForkJoinTask 的不同子类来完成任务:
RecursiveAction:用于任务没有返回结果的场景。
RecursiveTask:用于任务有返回结果的场景。
上面的例子中我们就是继承了RecursiveAction子类用于没有返回结果的场景,下面我们再看一下RecursiveTask用于有返回结果的场景:
public class TestRecursiveTask {
public static void main(String[] args) {
Integer result = 0;
ForkJoinPool pool = new ForkJoinPool();
Future<Integer> future = pool.submit(new SumTask(30));
try {
result = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(result+"===========================");
}
}
class SumTask extends RecursiveTask<Integer> {
int num;
public SumTask(int num) {
this.num = num;
}
@Override
protected Integer compute() {
if(num <= 20){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产完成"+num+"个产品");
return num;
}else{
SumTask task1 = new SumTask(20);
SumTask task2 = new SumTask(num - 20);
task1.fork();
task2.fork();
return task1.join() + task2.join();
}
}
}
结果为:
生产完成20个产品
生产完成10个产品
30===========================
Process finished with exit code 0
我们看到继承RecursiveTask类指定了返回值类型为Integer,在compute方法中的返回值类型即为Integer类型。
从以上的例子中可以看到,通过使用 Fork/Join 模式,软件开发人员能够方便地利用多核平台的计算能力。尽管还没有做到对软件开发人员完全透明,Fork/Join 模式已经极大地简化了编写并发程序的琐碎工作。对于符合 Fork/Join 模式的应用,软件开发人员不再需要处理各种并行相关事务,例如同步、通信等,以难以调试而闻名的死锁和 data race 等错误也就不会出现,提升了思考问题的层次。你可以把 Fork/Join 模式看作并行版本的 Divide and Conquer 策略,仅仅关注如何划分任务和组合中间结果,将剩下的事情丢给 Fork/Join 框架。
java并发编程(十八)----(线程池)java线程池框架Fork-Join的更多相关文章
- Java并发编程(八):线程调度——线程池
new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { / ...
- Java并发编程(二)如何保证线程同时/交替执行
第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜.本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法. ...
- 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结
<Java并发编程实战>和<Java并发编程的艺术> Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...
- Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系
CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的 ...
- Java并发(十八):阻塞队列BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...
- Java并发编程(五):Java线程安全性中的对象发布和逸出
发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...
- java并发编程JUC第九篇:CountDownLatch线程同步
在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...
- Java并发编程实战 第16章 Java内存模型
什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...
- java并发编程实战《二》java内存模型
Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...
- Java并发编程(三)什么是线程池
什么是线程池 学习编程的小伙伴们会经常听到“线程池”.“连接池”这类的词语,可是到底“池”是什么意思呢?我讲个故事大家就理解了:在很久很久以前有一家银行,一年之中只有一个客户来办理业务,随着时间的推移 ...
随机推荐
- 百度云下载神器 速盘SpeedPan v1.9.7
速盘 – 不一样的度盘神器!SpeedPan 是一款由吾爱破解论坛会员"菩提叶"制作的度盘满速下载工具.这款百度网盘高速下载工具,免费小巧简单易用,采用了Aria2多线程下载,支持 ...
- 从无到有构建vue实战项目(五)
八.错误总结(一) webpack打包项目识别子组件路径问题 之所以出现了这样的问题是因为在webpack打包项目时,未将此处的子组件路径正确识别: 将此处的carousel改为carousel.vu ...
- Jenkins高级应用——Publish Over SSH插件
在之前的篇幅中有介绍,利用jenkins结合shell脚本完成远程部署的功能,点击Jenkins结合shell+expect脚本完成线上环境自动构建部署即可查看. 上述方式需要引入expect脚本,安 ...
- C#实现某一属性值变化时触发事件
在我们做工业软件中,经常会遇到要实时监控某一点,在这个点变化时去做一些事情 放入程序里呢,就是要实时监控某一属性的值,当值发生变化时触发事件,其核心就是借助属性的Set方法,来判断当前set的值是否与 ...
- 数据结构与算法---堆排序(Heap sort)
堆排序基本介绍 1.堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序. 2.堆是具有以下性质的完全二叉树:每个 ...
- py+selenium IE 用driver.close()却把两个窗口都关了【已解决】
环境:py3 selenium unittest 测试浏览器:IE10 目标:在单个文件中,有多个用例,执行完A用例,由于打开了新的窗口,必须关闭新的窗口,才不会影响下一条用例的执行. 问题:按例 ...
- python迭代器-迭代器取值-for循环-生成器-yield-生成器表达式-常用内置方法-面向过程编程-05
迭代器 迭代器 迭代: # 更新换代(其实也是重复)的过程,每一次的迭代都必须基于上一次的结果(上一次与这一次之间必须是有关系的) 迭代器: # 迭代取值的工具 为什么用迭代器: # 迭代器提供了一种 ...
- Java EE.JSP.指令
JSP的指令是从JSP向Web容器发送消息,它用来设置页面的全局属性,如输出内容类型等. JSP的指令的格式为:<%@ 指令名 属性="属性值"%> 1.page指令 ...
- 基础篇-1.2Java世界的规章制度(下)
1 Java运算符 Java世界中的运算其实就是数学运算,而运算符就是其中的媒介. 算术运算符 操作符 描述 + 加法,对符号两边的数值相加 - 减法,符号左边的数减去右边的数 * 乘法,符号两边的数 ...
- php laravel5.6引入geetest 行为验证
php laravel5.6引入geetest 行为验证 使用必要性 网站和APP,在所有可能被机器行为攻击的场景,例如但不限于注册.登录.短信接口.查询接口.营销活动.发帖评论等等,都可以部署使用「 ...