Fork译为拆分,Join译为合并
Fork/Join框架的思路是把一个非常巨大的任务,拆分成若然的小任务,再由小任务继续拆解。直至达到一个相对合理的任务粒度。然后执行获得结果,然后将这些小任务的结果汇总,生成大任务的结果,
直至汇总成最初巨大任务的结果。如下图:

红色箭头代表拆分子任务。
绿色箭头代表返回子任务结果
这个框架的思路听起来,其实用传统的线程池、多线程完全就可以解决。但是内部却有很多小的细节(后边会说到),再加上清晰的使用思路,让这个框架还是在多线程并发中,占有了一席之地。
Fork/Join框架下,我们常用到三个类:(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
RecursiveAction,子任务类,支持子任务有返回结果任务
RecursiveTask,子任务类,用于有返回结果的任务
ForkJoinPool,执行子任务的线程池。
话不多说,我们直接看代码:

 1 public class SumDemo extends RecursiveTask<Long> {
2
3 int maxLen = 800_0000;
4
5 int[] arr;
6 int start;
7 int end;
8
9
10 public SumDemo(int[] arr, int start, int end) {
11 this.arr = arr;
12 this.start = start;
13 this.end = end;
14 }
15
16 @Override
17 protected Long compute() {
18 if (end - start < maxLen) {
19 long a = sum();
20 try {
21 //Thread.sleep(1);
22 } catch (Exception e) {
23 }
24 return a;
25 }
26 int middle = (start + end) / 2;
27 SumDemo left = new SumDemo(arr, start, middle);
28 SumDemo right = new SumDemo(arr, middle + 1, end);
29 left.fork();
30 right.fork();
31 //invokeAll(left,right);
32 long leftRtn = left.join();
33 long rightRtn = right.join();
34 return leftRtn + rightRtn;
35 }
36
37 private Long sum() {
38 System.out.println("now" + Thread.currentThread().getName() + "-start:" + start + "-end:" + end);
39 long sum = 0;
40 for (int i = start; i <= end; i++) {
41 sum += arr[i];
42 }
43 return sum;
44 }
45
46 public static void main(String[] args) throws ExecutionException, InterruptedException {
47 int size = 30000_0000;
48 int[] arr = new int[size];
49 Random random = new Random(0);
50 for (int i = 0; i < size; i++) {
51 arr[i] = random.nextInt(10_0000_0000);
52 }
53 long cal = 0;
54 long start = System.currentTimeMillis();
55 for (int i = 0; i < size; i++) {
56 if (i % 800_0000 == 0) {
57 Thread.sleep(1);
58 }
59 cal += arr[i];
60 }
61 long finish = System.currentTimeMillis();
62 long timeCost = finish - start;
63 System.out.println("cal" + cal);
64 long start1 = System.currentTimeMillis();
65 ForkJoinPool forkJoinPool = new ForkJoinPool();
66 ForkJoinTask<Long> result = forkJoinPool.submit(new
67 SumDemo(arr, 0, size - 1));
68 long rtn = result.get();
69 long finish1 = System.currentTimeMillis();
70 long forkJoinCost = finish1 - start1;
71 System.out.println("one thread cost" + (timeCost));
72 System.out.println("fork join cost" + forkJoinCost);
73 }
74 }

执行的结果大概是这样的

 1 cal150000314007254036
2 nowForkJoinPool-1-worker-1-start:0-end:4687499
3 nowForkJoinPool-1-worker-3-start:187500000-end:192187499
4 nowForkJoinPool-1-worker-5-start:37500000-end:42187499
5 nowForkJoinPool-1-worker-6-start:225000000-end:229687499
6 .....
7 nowForkJoinPool-1-worker-3-start:220312500-end:224999999
8 nowForkJoinPool-1-worker-7-start:267187500-end:271874999
9 nowForkJoinPool-1-worker-2-start:107812500-end:112499999
10 nowForkJoinPool-1-worker-4-start:281250000-end:285937499
11 nowForkJoinPool-1-worker-7-start:271875000-end:276562499
12 nowForkJoinPool-1-worker-5-start:135937500-end:140624999
13 nowForkJoinPool-1-worker-11-start:140625000-end:145312499
14 nowForkJoinPool-1-worker-6-start:276562500-end:281249999
15 nowForkJoinPool-1-worker-4-start:285937500-end:290624999
16 nowForkJoinPool-1-worker-11-start:145312500-end:149999999
17 nowForkJoinPool-1-worker-7-start:290625000-end:295312499
18 nowForkJoinPool-1-worker-4-start:295312500-end:299999999
19 one thread cost136
20 fork join cost67

线程池默认大小是根据cpu当前的可用核数来作为大小的,我们这里是12核,但是12核居然只比单一线程用时少50%,这是挺奇怪的,这主要是由于我们Demo中的任务是连续的计算密集型任务,这种情况下单一线程的表现也很优秀,forkJoin反而由于要不断协调线程

任务而导致会损耗性能,所以差距并不明显。倘若放开注释中的睡眠时间,则两者的差距会拉开的非常大,如下:

1 one thread  cost675
2 fork join cost194

代码的思路大概是这样的:

我们先定义一个子任务类,子任务类设置一个阈值,子任务开始任务时会判断:
如果计算量未超过阈值呢,说明任务足够小,我们当前子任务直接就执行计算了。
如果计算量超过阈值,说明任务比较大我们需要进行拆分,此时创建好拆分子任务,并使用fork()方法即可。拆分后的子任务,则后续使用join等待结果即可。
这样通过Fork/Join框架实现大任务的计算就算是搞定了。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

那既然是线程池,是如何协调线程来计算子任务的呢?

(1)与传统线程池共享一个任务队列不同的是,Fork/Join框架中,每个子任务都有一个属于自己线程的任务队列(但是两者其实并不是一对一的关系,源码很复杂),如下图:

这样肯定会由于任务规模、计算难度的不同,导致有些线程很快执行完了,其它线程还有很长的任务队列,那怎么办呢?
Fork/Join框架会让任务已经完成的线程,从其它任务的队列的尾端去取任务,这样一方面加速了任务的完成,一方面又减少了线程由于并发操作队列可能存在的并发问题。
这种方式,我们也将它称为“工作窃取”如下图:

(2)Fork出来的子任务被谁执行了:
通过阅读源码我们可以发现,如果当前线程是线程池线程,则直接把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 }

无限分解流----Fork/Join框架的更多相关文章

  1. 三、并行流与串行流 Fork/Join框架

    一.并行流概念: 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流. java8中将并行进行了优化,我们可以很容易的对数据进行并行操作.Stream API可以声明性的通过pa ...

  2. Fork/Join框架与Java8 Stream API 之并行流的速度比较

    Fork/Join 框架有特定的ExecutorService和线程池构成.ExecutorService可以运行任务,并且这个任务会被分解成较小的任务,它们从线程池中被fork(被不同的线程执行)出 ...

  3. 013-多线程-基础-Fork/Join框架、parallelStream讲解

    一.概述 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 它同ThreadPoolExecut ...

  4. Java开发笔记(一百零六)Fork+Join框架实现分而治之

    前面依次介绍了普通线程池和定时器线程池的用法,这两种线程池有个共同点,就是线程池的内部线程之间并无什么关联,然而某些情况下的各线程间存在着前因后果关系.譬如人口普查工作,大家都知道我国总人口为14亿左 ...

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

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

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

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

  7. Java 7 Fork/Join 框架

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

  8. 《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验

    JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多线程的方式来执行一个任务. ...

  9. 多线程(五) Fork/Join框架介绍及实例讲解

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

  10. Java8新特性 并行流与串行流 Fork Join

    并行流就是把一个内容分成多个数据块,并用不同的线程分 别处理每个数据块的流. Java 8 中将并行进行了优化,我们可以很容易的对数据进行并 行操作. Stream API 可以声明性地通过 para ...

随机推荐

  1. 来自jackson的灵魂一击:@ControllerAdvice就能保证万无一失吗?

    前几天写了篇关于fastjson的文章,<fastjson很好,但不适合我>.里面探讨到关于对象循环引用的序列化问题.作为spring序列化的最大竞品,在讨论fastjson的时候肯定要对 ...

  2. Django笔记十五之in查询及date日期相关过滤操作

    这一篇介绍关于范围,日期的筛选 in range date year week weekday quarter hour 1.in in 对应于 MySQL 中的 in 操作,可以接受数组.元组等类型 ...

  3. go微服务框架kratos学习笔记二(kratos demo 结构)

    目录 api cmd configs dao di model server service 上篇文章go微服务框架kratos学习笔记一(kratos demo)跑了kratos demo 本章来看 ...

  4. Python中实现单例的几种方式

    Python如何实现单例? 什么是单例模式? 单例模式:一个类只能有一个实例化对象存在的模式. 如何实现单例? 1.使用模块 python中模块是天然的单例模式,当一个模块被调用时,会生成对应的.py ...

  5. [Nginx/Linux]Nginx从1.15.12平滑升级到1.17.5

    1 问题背景 nginx 安全漏洞(CVE-2019-9511) nginx 安全漏洞(CVE-2019-9513) nginx 安全漏洞(CVE-2019-9516) http://www.cnnv ...

  6. Express实现定时发送邮件

    在开发中我们有时候需要每隔 一段时间发送一次电子邮件,或者在某个特定的时间进行发送邮件, 无需手动去操作,基于这样的情况下我们需要用到了定时任务,一般可以写个定时器,来完成相应的需求,在 node.j ...

  7. Numpy浅拷贝与深拷贝

    Numpy中的浅拷贝与深拷贝 浅拷贝 共享内存地址的两个变量,当其中一个变量的值改变时另外一个也随之改变. Example a = np.array([1, 2, 3, 4, 5]) b = a pr ...

  8. 从0到1手把手教你ASP.NET Core Web API项目配置接口文档Swagger(二)

    传送门:从0到1手把手教你ASP.NET Core Web API项目配置接口文档Swagger(一) 一.设置Swagger页面为首页--开发环境 我们虽然可以在输入 /swagger 后顺利的访问 ...

  9. pygame:简易游戏(飞机大战)

    import math import random import pygame import sys # pygame初始化 pygame.init() # 得分初始化 score = 0 # 创建s ...

  10. RTSP Server(LIVE555)源码分析(四)-SETUP信令

    主要分析RTSPServer::RTSPClientSession针对客户端SETUP事件处理 一. SETUP信令,handleCmd_SETUP源码解析 1)步骤1.03,parseTranspo ...