本文部分摘自《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. Web 前端如何一键开启上帝模式

    Web 前端如何一键开启上帝模式 God Mode document.designMode = `on`; refs https://www.cnblogs.com/xgqfrms/tag/desig ...

  2. JavaScript for, for...in, for...of, for-await...of difference All In One

    JavaScript for, for...in, for...of, for-await...of difference All In One for for...in for...of for-a ...

  3. what's the print number means after called the setTimeout function in Chrome console?

    what's the print number means after called the setTimeout function in Chrome console? javascript fun ...

  4. HTML Imports & deprecated

    HTML Imports & deprecated https://caniuse.com/#search=html imports https://www.chromestatus.com/ ...

  5. NGK的发行量是多少?NGK销毁机制是怎么样的?

    代币销毁(Coin Burning),是指将代币从流通中永久性去除.换句话说,被销毁的代币相当于被永久性冻结,再也无法流入市场.那为什么要进行代币销毁呢? 销毁加密货币,可以使剩余加密货币的价值升高, ...

  6. ClickHouse源码笔记3:函数调用的向量化实现

    分享一下笔者研读ClickHouse源码时分析函数调用的实现,重点在于分析Clickhouse查询层实现的接口,以及Clickhouse是如何利用这些接口更好的实现向量化的.本文的源码分析基于Clic ...

  7. short i=1;i=i+1;为什么报错?

    先测试,看结果: 提示我们说不能将short类型的转化为int类型! 先不急着下结论,我们继续测试,用i+=1; 我们发现并没有报错,为什么同样是加1,会出现这样两种不同的结果呢? 查阅了一些资料,大 ...

  8. Get optimized undo_retention size for Oracle

    reference: https://www.akadia.com/services/ora_optimize_undo.html#:~:text=Turning%20on%20automatic%2 ...

  9. 《C++ Primer》笔记 第12章 动态内存

    shared_ptr和unique_ptr都支持的操作 解释 shared_ptr sp或unique_ptr up 空智能指针,可以指向类型为T的对象 p 将p用作一个条件判断,若p指向一个对象,则 ...

  10. 人脸检测数据源制作与基于caffe构架的ALEXNET神经网络训练

    本篇文章主要记录的是人脸检测数据源制作与ALEXNET网络训练实现检测到人脸(基于caffe). 1.数据获取 数据获取: ① benchmark是一个行业的基准(数据库.论文.源码.结果),例如WI ...