概述

Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
下图是网上流传的 Fork Join 的运行流程图,直接拿过来用了:

工作窃取算法

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
那么为什么要使用这个算法呢?
假如我们需要做一个比较大的任务,可以把这个任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如A线程负责处理A队列里的任务。但是,有的线程会先把自己队列里的任务干完,而其他线程队列对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优缺点:

  • 优点:充分利用线程进行并行计算,减少了线程间的竞争。
  • 缺点:在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且该算法会消耗了更多的系统资源,比如创建多个线程和多个双端队列。

Fork/Join 框架工作流程

首先,是分割任务。需要有个fork类来把大任务分割成子任务,有可能子任务还是很大,那么还需要不停的分割,知道分割的任务足够小。
然后,执行任务的合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。

Fork/Join 框架的使用

Fork/Join 的使用需要使用两个类:

  • ForkJoinTask:要使用 Fork/Join 框架首先要创建一个 ForkJoin 任务,它提供在任务中执行 fork() 和 join() 操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的子类,Fork/Join 框架提供了下面两个子类:
    • RecursiveAction:用于没有返回结果的任务。
    • RecursizeTask:用于有返回结果的任务。
  • ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行。

任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列暂时没有其他任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
下面我们通过一个简单的例子来了解一下 Fork/Join 框架的使用:计算 1+2+3+…..+10的结果。
使用 Fork/Join 框架首先要考虑的是如何分割任务,这个需要我们在代码里面实现。
因为这个是需要返回结果的任务,因此只能使用 RecursiveTask 来实现。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
53
54
55
56
public static void testForkJoin() {
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 生成一个计算任务
CountTask countTask = new CountTask(1, 10);
// 执行任务
Future<Integer> result = forkJoinPool.submit(countTask);
int sum = 0;
try {
sum = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Log.e("Test","sum = "+sum);
}
public static class CountTask extends RecursiveTask<Integer> {
// 设置一个阈值
private static final int THRESHOLD = 2;
private int mStart;
private int mEnd;
public CountTask(int start, int end) {
mStart = start;
mEnd = end;
Log.e("Test", "CountTask start = "+start+", end= = "+end);
}
@Override
protected Integer compute() {
int sum = 0;
boolean canCompute = (mEnd - mStart) <= THRESHOLD;
if (canCompute) {
for(int i = mStart; i<=mEnd;i++){
sum += i;
}
} else {
// 如果大于阈值,就要继续分割任务
int middle = (mStart + mEnd) / 2;
CountTask leftTask = new CountTask(mStart, middle);
CountTask rightTask = new CountTask(middle + 1, mEnd);
// 执行子任务
leftTask.fork();
rightTask.fork();
//等待子任务执行完,并获取执行结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 合并子任务
sum = leftResult + rightResult;
}
return sum;
}
}

ForkJoinTask 与一般任务的主要区别在于它需要实现 compute 方法,在这个方法里面,首先要判断任务是否足够小,如果足够小就直接执行任务,如果不够小,就必须分割成两个子任务,每个子任务在调用 fork 方法时,又会进入 compute 方法,看看当前子任务是否需要继续分割成子任务,如果不继续分割,则执行当前任务并返回结果。使用 join 方法会等待任务执行完并得到其结果。

Fork/Join 框架的异常处理

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

1
2
3
4
5
if (leftTask.isCompletedAbnormally()) {
if (leftTask.getException() != null ) {
leftTask.getException().printStackTrace();
}
}

getException() 方法返回 Throwable 对象,如果任务取消了则返回 CancellationException。如果任务没有完成或者没有抛出异常则返回 null。

Fork/Join 框架的实现原理

参考

《Java 并发编程的艺术》

Java 并发编程 -- Fork/Join 框架的更多相关文章

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

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

  2. 【fork/join】java并发编程-fork/join示例

    package com.chinamobile.epic.tako.common.graphite.query.sync.impl; import com.google.common.collect. ...

  3. java 中的fork join框架

    文章目录 ForkJoinPool ForkJoinWorkerThread ForkJoinTask 在ForkJoinPool中提交Task java 中的fork join框架 fork joi ...

  4. Java 并发之 Fork/Join 框架

    什么是 Fork/Join 框架 Fork/Join 框架是一种在 JDk 7 引入的线程池,用于并行执行把一个大任务拆成多个小任务并行执行,最终汇总每个小任务结果得到大任务结果的特殊任务.通过其命名 ...

  5. JAVA中的Fork/Join框架

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

  6. Java并发编程之——Amino框架

    Amino框架是一个采用无锁方式实现并行计算的框架,可惜的是,网上关于Amino框架的介绍甚少.根据所掌握的资料,稍微总结一下: 1. 锁机制到无锁机制 锁机制可以确保程序和数据的线程安全,但是锁是一 ...

  7. Java并发编程--4.Executor框架

    简介 Executor框架是启动,管理线程的API, 它的内部实现是线程池机制,它有很多好处,比如使任务提交和任务执行解耦合,防止this逃逸:它的主要API包括: Executor,  Execut ...

  8. Java并发编程(08):Executor线程池框架

    本文源码:GitHub·点这里 || GitEE·点这里 一.Executor框架简介 1.基础简介 Executor系统中,将线程任务提交和任务执行进行了解耦的设计,Executor有各种功能强大的 ...

  9. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

随机推荐

  1. SQL注入常用函数(注入小白的学习笔记)

    在盲注的情况下,往往需要一个一个字符的去猜解,即过程中需要截取字符串 在这里整理了一下一些常用函数 由于现阶段学习不够深入,整理分类不清楚具体,不过博主会慢慢进行完善 user() 查询当前数据库用户 ...

  2. KeyError: 'xxx does not support field: _id'

    Scrapy存储爬取的数据时,提示不支持某些字段 在Item文件中,添加对应的字段即可

  3. 学习spring第三天

    Spring第三天笔记 今日内容 Spring的核心之一 -  AOP思想 (1) 代理模式- 动态代理 ① JDK的动态代理 (Java官方) ② CGLIB 第三方代理 (2) AOP思想在Spr ...

  4. JS 2019-12-03T15:53:23.000+08:00 转化为 YYYY MM DD

    js时间格式转化 2019-12-03T15:53:23.000+08:00 转化为 YYYY MM DD var dateee = new Date(createTime).toJSON();var ...

  5. linux下别名的设定

    命令别名设定功能: (alias)假如我需要知道这个目录底下的所有文件 (包含隐藏档) 及所有的文件属性,那么我就必须要下达『 ls -al 』这样的指令串,比较麻烦,我们可以为其设定别名为lm al ...

  6. 发生 Configuration system failed to initialize 错误的一个特例

    一般情况下,.net 程序启动时发生 Configuration system failed to initialize 错误, 大都与 config 文件中 <configSections&g ...

  7. 38)PHP,获取数据库数据并在html中显示(晋级5)

    还有一个加了单例模式的,在第52个. 首先是我的文件关系: 我的主php文件是index.php,我的配置文件php是BBB.php  我的数据库操作文件是  b.php    我的html文件是lo ...

  8. 透过F5获取服务器真实内网IP

    渗透测试过程中,经常会遇到目标服务器使用F5 LTM做负载均衡. 如果能获取到目标服务器的真实IP地址,会给后续渗透带来一定便利. 本文既是最近渗透遇到的一点点经验分享. F5修改cookie机制 F ...

  9. PAT甲级——1006 Sign In and Sign Out

    PATA1006 Sign In and Sign Out At the beginning of every day, the first person who signs in the compu ...

  10. Java基础语法要点

    1.Java中byte.short.int.long的取值范围 byte:[-128,127] short:[-32768,32767] int:[-2147483648,2147483647] lo ...