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. ChatGPT3.5使用体验

    优点 1.ChatGPT 能颠覆现有的搜索引擎(百度.谷歌). 2.ChatGPT 的交互体验非常好,满足"智能助手"这种工具. 3.如何使用好ChatCPT? 回到一个经典的问题 ...

  2. NoSQL之 Redis配置与优化

    目录 一.缓存概念 1.1 系统缓存 1.1.1buffer与cache 1.2 缓存保存位置及分层结构 1.2.1 DNS缓存 1.2.2 应用层缓存 1.2.3数据层缓存 1.2.4 硬件缓存 二 ...

  3. java开发技术栈如何选型

    前言 2023泰山景区门票免费政策是从1月21日到3月31,今天4.1起不再免费啦,泰山的人.山和系统终于平安的渡劫过去! 洪峰时疯狂的抢票.各类攻击,分销MT两次凌晨抗洪事件,我及其我的团队又一次得 ...

  4. 在k8s(kubernetes) 上安装 ingress V1.1.0

    Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由.流量路由由 Ingress 资源上定义的规则控制. 下面是一个将所有流量都发送到同一 Service 的简单 Ingre ...

  5. JMeter-BeanShell预处理程序和BeanShell后置处理程序的应用

    一.什么是BeanShell? BeanShell是用Java写成的,一个小型的.免费的.可以下载的.嵌入式的Java源代码解释器,JMeter性能测试工具也充分接纳了BeanShell解释器,封装成 ...

  6. layUI之树状表格异步加载组件treetableAsync.js(基于treetable.js)

    目录 概述 1. 使用说明 2. 使用需知 2.1 本组件依赖于treetable.js[重中之重] 2.2 本组件基于layUIAdmin进行使用 2.3 本组件的方法支持treetable.js的 ...

  7. FFmpeg开发笔记(一)搭建Linux系统的开发环境

    对于初学者来说,如何搭建FFmpeg的开发环境是个不小的拦路虎,因为FFmpeg用到了许多第三方开发包,所以要先编译这些第三方源码,之后才能给FFmpeg集成编译好的第三方库.不过考虑到刚开始仅仅调用 ...

  8. c语言趣味编程(4)抓交通肇事犯

    一.问题描述 一辆卡车违反交通规则,撞人后逃跑.现场有三人目击该事件,但都没有记住车号,只记下车号的一些特征. 甲说:牌照的前两位数字是相同的: 乙说:牌照的后两位数字是相同的,但与前两位不同: 丙是 ...

  9. 百度飞桨(PaddlePaddle)安装

    注意:32位pip没有PaddlePaddle源 # 如果报下列错误,检查 Python 版本,不能过高也不要太低,并且不能是 32位的. ERROR: Could not find a versio ...

  10. 【Docker】Harbor 分布式仓库管理

    一.Harbor 介绍 Harbor 是 VMware 公司开源的企业级 Docker Registry 项目,其目标是帮助用户迅速搭建一个企业级的 Docker Registry (私有仓库)服务. ...