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

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

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

执行的结果大概是这样的

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

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

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

  1. 1 one thread cost675
  2. 2 fork join cost194

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

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

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

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

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

(2)Fork出来的子任务被谁执行了:
通过阅读源码我们可以发现,如果当前线程是线程池线程,则直接把fork出的子任务丢到当前线程的队列中,否则会通过计算随机的提交到其他的线程所拥有的的队列中。由其他线程来完成。

  1. 1 public final ForkJoinTask<V> fork() {
  2. 2 Thread t;
  3. 3 if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
  4. 4 ((ForkJoinWorkerThread)t).workQueue.push(this);
  5. 5 else
  6. 6 ForkJoinPool.common.externalPush(this);
  7. 7 return this;
  8. 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. Go语言:通过TDD驱动测试开发为同事写的程序优化提速——初次接触并发与channel

    正文: 假如同事已经写了一个 CheckWebsites 的函数检查 URL 列表的状态. package concurrency type WebsiteChecker func(string) b ...

  2. 一文总结你需要的OpenCV操作

    目录 一.OpenCV简介 1.1 OpenCV是什么 1.2 安装及使用 二.图像的基础 2.1 成像原理 2.2 图像格式 2.3 颜色空间 三.OpenCV基础操作 3.1 图像的读取.显示.保 ...

  3. OSM(OpenStreetMap)全球路网数据下载方式介绍

      本文对OpenStreetMap(OSM)网页与各类OSM数据的多种下载方式加以详细介绍,并对不同数据下载方式加以对比.   OSM数据包含道路与铁路路网.建筑.水体.土地利用.兴趣点.行政区边界 ...

  4. data.frame数据框操作——R语言

    统计分析中最常见的原始数据形式是类似于数据库表或Excel数据表的形式. 这样形式的数据在R中叫做数据框(data.frame). 数据框类似于一个矩阵,但各列允许有不同类型:数值型向量.因子.字符型 ...

  5. python入门教程之二十一json操作

    JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式 python3 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数: json. ...

  6. 【JSOI2008】最大值

    [JSOI2008]最大值 线段树裸题!动态RMQ. 这道题的操作是直接在序列末尾添加数值,所以连\(push_{down}\),以及建树什么的都不用了.. 这真是写过的最简短的一道\(seg_{tr ...

  7. Docker中Nginx搭建以及配置

    docker nginx搭建 1 docker pull nginx docker pull nginx 2 启动nginx docker run --name nginx -p 80:80 -d n ...

  8. React框架使用

    一:使用Vite创建React项目 二:React中组件使用 import React, { Component, useState } from "react"; //使用cla ...

  9. Mysql8.0为什么取消了缓存查询的功能

    首先我们介绍一下MySQL的缓存机制 [MySQL缓存机制]简单的说就是缓存sql文本及查询结果,如果运行完全相同的SQL,服务器直接从缓存中取到结果,而不需要再去解析和执行SQL. 但如果表中任何数 ...

  10. java无效发源版本xx

    这三个地方统一一下 就可以解决了