本文部分摘自《Java 并发编程的艺术》

Fork/Join 框架概述

Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架,其运行流程如图所示:

工作窃取算法

工作窃取算法是指某个线程从其他队列里窃取任务来执行,为什么要这样做呢?假如我们需要做一个比较大的任务,可以把这个任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。然而,如果某一线程先把自己队列的任务干完了,而其他线程对应的队列里还有任务等待处理,干完活的线程与其等着,不如去帮其他线程干活,这就是工作窃取算法的动机。

为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行

使用 Fork/Join 框架

首先思考一下,如果让我们来设计一个 Fork/Join 框架,该如何设计呢?

  1. 分割任务

    首先我们需要一个有 fork 类来把大任务分割成子任务,有可能子任务还是很大,所以需要不停地分割,直到分割出来的子任务足够小

  2. 执行任务并合并结果

    分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后进行合并

Fork/Join 使用两个类来完成以上两件事情:

  1. ForkJoinTask

    我们使用 ForkJoin 框架,必须首先创建 ForkJoin 任务,它提供在任务中执行 fork() 和 join() 操作的机制。通常情况下,我们不需要直接继承 ForkJoinTask 类,只需要继承它的子类即可,Fork/Join 框架提供了以下两个子类:

    • RecursiveAction:用于没有返回结果的任务
    • RecursiveTask:用于有返回结果待任务
  2. ForkJoinPool

    ForkJoinTask 需要通过 ForkJoinPool 来执行

我们通过一个简单的需求来使用 Fork/Join 框架,需求是:计算 1+2+3+4 的结果

使用 Fork/Join 框架把这个任务 fork 成两个子任务,子任务一负责计算 1+2,子任务而负责计算 3+4,然后再 join 两个子任务的结果,因为是有结果的任务,所以必须继承 RecursiveTask,代码实现如下:

public class CountTask extends RecursiveTask<Integer> {

    // 阈值
private static final int THRESHOLD = 2; private final int start;
private final 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 = leftTask.join();
int rightResult = 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<Integer> result = forkJoinPool.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

上面的例子中是通过 new ForkJoinPool(),然而这并不是其作者 Doug Lea 推荐的方式。ForkJoinPool 类有一个静态方法commonPool(),它所获得的 ForkJoinPool 实例是由整个应用进程共享的,可以帮助应用程序中多个需要进行归并计算的任务共享计算资源

ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

ForkJoinTask 在执行的时候可能会抛出异常,但我们没办法在主线程直接捕获线程,所以 ForkJoinTask 提供了 isCompletedAbnormally() 方法来检查任务是否已经抛出异常或已经被取消,并可以通过 ForkJoinTask 的 getException 方法获取异常

if(task.isCompletedAbnormally()) {
System.out.println(task.getException());
}

Fork/Join 框架的实现原理

ForkJoinPool 中用来处理任务的工作线程采用的是 ForkJoinWorkerThread,它继承了 Thread 类,拥有两个非常关键的变量

final ForkJoinPool pool;
final ForkJoinPool.WorkQueue workQueue;

pool 是这个工作线程所属的 ForkJoinPool 实例,workQueue 是一个双端队列,可以发现,它是 ForkJoinPool 的一个内部类,其结构如下(省略部分代码)

static final class WorkQueue {
...
ForkJoinTask<?>[] array;
final ForkJoinPool pool;
final ForkJoinWorkerThread owner;
...
}

WorkQueue 里维护一个 ForkJoinTask 数组,用来存放待执行的任务(ForkJoinTask)。所以 Fork/Join 框架的基本思想就是:ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),里面存放的对象是任务,每个工作线程处理自己的工作队列里的任务

fork() 方法做的工作只有一件事,既是把任务推入当前工作线程的工作队列里

public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}

join() 方法的工作则复杂一些,首先会判断线程是否为 ForkJoinThread 线程,如果不是,阻塞当前线程,等待任务完成,如果是,则不阻塞。接着查看任务的完成状态,如果已经完成,直接返回结果,否则从队列中取出任务执行

Fork/Join 框架的更多相关文章

  1. JAVA中的Fork/Join框架

    看了下Java Tutorials中的fork/join章节,整理下. 什么是fork/join框架 fork/join框架是ExecutorService接口的一个实现,可以帮助开发人员充分利用多核 ...

  2. 聊聊并发(八)——Fork/Join框架介绍

      作者 方腾飞 发布于 2013年12月23日 | 被首富的“一个亿”刷屏?不如定个小目标,先把握住QCon上海的优惠吧!2 讨论 分享到:微博微信FacebookTwitter有道云笔记邮件分享 ...

  3. JDK7新特性之fork/join框架

    The fork/join framework is an implementation of the ExecutorService interface that helps you take ad ...

  4. Java并发——Fork/Join框架

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4631466. ...

  5. 转:聊聊并发(八)——Fork/Join框架介绍

    1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过 ...

  6. Java并发编程--Fork/Join框架使用

    上篇博客我们介绍了通过CyclicBarrier使线程同步,可是上述方法存在一个问题,那就是假设一个大任务跑了2个线程去完毕.假设线程2耗时比线程1多2倍.线程1完毕后必须等待线程2完毕.等待的过程线 ...

  7. 使用Java7提供Fork/Join框架

    在Java7在.JDK它提供了多线程开发提供了一个非常强大的框架.这是Fork/Join框架.这是原来的Executors更多 进一步,在原来的基础上添加了并行分治计算中的一种Work-stealin ...

  8. 使用Java7提供的Fork/Join框架

    http://blog.csdn.net/a352193394/article/details/39872923 使用Java7提供的Fork/Join框架 2014-10-07 23:55 4818 ...

  9. 实现ThreadFactory接口生成自定义的线程给Fork/Join框架

    Fork/Join框架是Java7中最有趣的特征之一.它是Executor和ExecutorService接口的一个实现,允许你执行Callable和Runnable任务而不用管理这些执行线程.这个执 ...

  10. Java 7 Fork/Join 框架

    在 Java7引入的诸多新特性中,Fork/Join 框架无疑是重要的一项.JSR166旨在标准化一个实质上可扩展的框架,以将并行计算的通用工具类组织成一个类似java.util中Collection ...

随机推荐

  1. 动态规划算法 All In One

    动态规划算法 All In One dynamic programming leetcode https://leetcode.com/tag/dynamic-programming/ https:/ ...

  2. css & focus-within & pseudo class

    css & focus-within & pseudo class demo :focus-within https://developer.mozilla.org/en-US/doc ...

  3. DOM事件对象用法

    分为三个阶段:事件捕获阶段.目标阶段.事件冒泡阶段. 事件捕获老版本浏览器(IE<=8)不支持,但是事件冒泡可以放心使用. 事件处理程序 一共四类写法,基本都见过,看下写法就知道怎么回事儿了. ...

  4. Scrapy 项目:腾讯招聘

    目的: 通过爬取腾讯招聘网站(https://careers.tencent.com/search.html)练习Scrapy框架的使用 步骤: 1.通过抓包确认要抓取的内容是否在当前url地址中,测 ...

  5. K8s炼气期(一)| minikube安装本地Kubenetes环境

    前言 根据Kubenetes学习路径的七大阶段,炼气期.筑基期.金丹期.元婴期.化神期.炼虚期.大乘期:开始炼气期的第一个小阶段,安装Kubenetes环境. 目录 1.安装kubectl 2.安装m ...

  6. ELK的一点认识

    为什么需要ELK: 一般大型系统是一个分布式部署的架构,不同的服务模块部署在不同的服务器上,问题出现时,大部分情况需要根据问题暴露的关键信息,定位到具体的服务器和服务模块,构建一套集中式日志系统,可以 ...

  7. Java基本概念:方法

    一.简介 描述: Java中方法是语句的集合,它们在一起执行一个功能. 方法是解决一类问题的步骤的有序组合,它在类中定义,属于类的成员,包含于类或对象中. 方法在程序中被创建后,在其他使用了该方法的地 ...

  8. TextView 的append后面 马上调用fullScroll(),会发现无法滚动到真正的底部

    如果在TextView的append后面马上调用fullScroll,会发现无法滚动到真正的底部,这是因为Android下很多(如果不是全部的话)函数都是基于消息的,用消息队列来保证同步,所以函数调用 ...

  9. Linux就该这样学--之常用linux命令及bash基础

    Linux就该这样学--之常用linux命令及bash基础 Linux命令 管道 重定向 环境变量 常用命令 常用系统工作命令 系统状态检测命令 工作目录切换命令 文本文件编辑命令 文件目录管理命令 ...

  10. WEB容器开启、关闭OPTIONS方法

    发现 请求包随意,响应包信息如下: HTTP/1.1 200 OK Cache-Control: private Content-Type: text/html; charset=utf-8 Vary ...