还记得我们在初始介绍线程池的时候提到了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的更多相关文章

  1. Java并发编程(八):线程调度——线程池

    new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { / ...

  2. Java并发编程(二)如何保证线程同时/交替执行

    第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜.本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法. ...

  3. 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结

    <Java并发编程实战>和<Java并发编程的艺术>           Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...

  4. Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系

    CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的 ...

  5. Java并发(十八):阻塞队列BlockingQueue

    阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...

  6. Java并发编程(五):Java线程安全性中的对象发布和逸出

    发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...

  7. java并发编程JUC第九篇:CountDownLatch线程同步

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  8. Java并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

  9. java并发编程实战《二》java内存模型

    Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...

  10. Java并发编程(三)什么是线程池

    什么是线程池 学习编程的小伙伴们会经常听到“线程池”.“连接池”这类的词语,可是到底“池”是什么意思呢?我讲个故事大家就理解了:在很久很久以前有一家银行,一年之中只有一个客户来办理业务,随着时间的推移 ...

随机推荐

  1. lambda匿名函数和他的小伙伴(处理大量数据的时候用到)

    lambda匿名函数 主要是为了解决一些简单的需求而设计的一句话函数 #计算n的n次方 def func(n): return n**n f = lambda n : n ** n 语法: 函数名 = ...

  2. 探寻 webpack_bundle_analyzer 原理

    webpack_bundle_analyzer 是什么? 这是webpack官方出品的,对项目中模块依赖关系及体积的分析插件,其界面如下: 问题来了,这是如何来进行统计的? 这里提供一个插件的代码,可 ...

  3. python接口自动化(三十)--html测试报告通过邮件发出去——中(详解)

    简介 上一篇,我们虽然已经将生成的最新的测试报告发出去了,但是MIMEText 只能发送正文,无法带附件,因此我还需要继续改造我们的代码,实现可以发送带有附件的邮件.发送带附件的需要导入另外一个模块 ...

  4. JSP之BBS论坛网站的创建

    游戏论坛 工具:myeclipse10.0版本 Tomacat是在外部导入的apache-tomcat-7.0.93 JDK版本为1.6.0_13 字符编码统一为utf-8 一.建立数据库(chat) ...

  5. Gin 框架 - 使用 logrus 进行日志记录

    目录 概述 日志格式 Logrus 使用 推荐阅读 概述 上篇文章分享了 Gin 框架的路由配置,这篇文章分享日志记录. 查了很多资料,Go 的日志记录用的最多的还是 github.com/sirup ...

  6. Web API POST [FromBody] string value 接受参数

    网上看到很多关于这这个问题的解决方案,但是都不正确,我也恰巧遇到这个问题,所有把正确的解决方案写出来,希望给后来人参考,如有不同意见欢迎指正 namespace WebApi.Controllers ...

  7. spring mvc 拦截器的使用

    Spring MVC 拦截器的使用 拦截器简介 Spring MVC 中的拦截器(Interceptor)类似于 Servler 中的过滤器(Filter).用于对处理器进行预处理和后处理.常用于日志 ...

  8. MsgWaitForMultipleObjects

    Use caution when calling the wait functions and code that directly or indirectly creates windows. If ...

  9. nginx 的信号量

    参考文章:https://blog.51cto.com/5660061/2380428 nginx 中的信号量: TERM,INT  快速的结束应用程序 ,等同于 kill -9 pid QUIT 优 ...

  10. vue使用问题总结(长期更新)

    循环中绑定标签的属性 <div class="imgdiv" v-for="template of templateArr"> <img :s ...