本文部分摘自《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,代码实现如下:

  1. public class CountTask extends RecursiveTask<Integer> {
  2. // 阈值
  3. private static final int THRESHOLD = 2;
  4. private final int start;
  5. private final int end;
  6. public CountTask(int start, int end) {
  7. this.start = start;
  8. this.end = end;
  9. }
  10. @Override
  11. protected Integer compute() {
  12. int sum = 0;
  13. boolean canCompute = (end - start) <= THRESHOLD;
  14. // 如果任务足够小就计算任务
  15. if (canCompute) {
  16. for (int i = start; i <= end; i++) {
  17. sum += i;
  18. }
  19. } else {
  20. // 如果任务大于阈值,就分裂成两个子任务计算
  21. int middle = (start + end) / 2;
  22. CountTask leftTask = new CountTask(start, middle);
  23. CountTask rightTask = new CountTask(middle + 1, end);
  24. // 执行子任务
  25. leftTask.fork();
  26. rightTask.fork();
  27. // 等待子任务执行完,并得到其结果
  28. int leftResult = leftTask.join();
  29. int rightResult = rightTask.join();
  30. // 合并子任务
  31. sum = leftResult + rightResult;
  32. }
  33. return sum;
  34. }
  35. public static void main(String[] args) {
  36. ForkJoinPool forkJoinPool = new ForkJoinPool();
  37. // 生成一个计算任务,负责计算 1+2+3+4
  38. CountTask task = new CountTask(1, 4);
  39. // 执行一个任务
  40. Future<Integer> result = forkJoinPool.submit(task);
  41. try {
  42. System.out.println(result.get());
  43. } catch (InterruptedException | ExecutionException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. }

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

  1. ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

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

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

Fork/Join 框架的实现原理

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

  1. final ForkJoinPool pool;
  2. final ForkJoinPool.WorkQueue workQueue;

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

  1. static final class WorkQueue {
  2. ...
  3. ForkJoinTask<?>[] array;
  4. final ForkJoinPool pool;
  5. final ForkJoinWorkerThread owner;
  6. ...
  7. }

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

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

  1. public final ForkJoinTask<V> fork() {
  2. Thread t;
  3. if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
  4. ((ForkJoinWorkerThread)t).workQueue.push(this);
  5. else
  6. ForkJoinPool.common.externalPush(this);
  7. return this;
  8. }

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. SSH Keys vs GPG Keys

    SSH Keys vs GPG Keys SSH Keys SSH keys allow you to establish a secure connection between your compu ...

  2. css animation & animation-fill-mode

    css animation & animation-fill-mode css animation effect https://developer.mozilla.org/en-US/doc ...

  3. React & react-native & vue & cli & environment information & report bugs

    React & react-native & vue & cli & environment information & report bugs cli che ...

  4. webIM & IM

    webIM & IM sdk 埋点 U-App AI https://developer.umeng.com/docs/67953/detail/68131 https://account.u ...

  5. puppeteer & screenshot

    puppeteer & screenshot http://localhost:9812/screenshot?url=https://cdn.xgqfrms.xyz/

  6. c++ 使用PID获取顶级窗口句柄和标题

    #include <iostream> #include <Windows.h> using namespace std; BOOL CALLBACK EnumWindowsP ...

  7. 精密进近OAS面的绘制与评估

    一.定义:精密进近OAS面(Obstacle Assessment Surface 障碍物评价面)是在精密进近程序中,用来对障碍物进行评估,找出影响运行标准的控制障碍物的一种计算方法. 二.构成 OA ...

  8. WPF -- 构建动画

    写在前面:本文代码摘自<Head First C#> 本文使用ObjectAnimationUsingKeyFrames + Storyboard构建一个动画. ObjectAnimati ...

  9. golang知识总结

    目录 1.slice扩容规则 2.内存寻址.内存对齐,go结构体内存对齐策略 3.go语言map类型分析 3.1 hash冲突 3.2 hash表扩容 3.3 go语言中的map结构是hash表. 3 ...

  10. MHA架构搭建中遇到的问题

    1. 两个包:mha4mysql-manager-0.56-0.el6.noarch.rpm 和 mha4mysql-node-0.56-0.el6.norch.rpm 地址:https://code ...