一丶Fork/Join框架产生背景:

随着并发需求的不断提高和硬件的不断发展,程序并行执行仿佛就提上日程上来了,伟大的毛主席就说过:“人多力量大”,所以如果一件事可以分配给多个人同时去做,到最后再把完成的事情组合到一起去,那么做事情的效率就会大大提升。用下面的这张图啦感受一下:



一件大事被分为五个处理器来处理(当然分开的这些任务没有依赖性),最终完成合并做成一件大事!

到这里,大家应该对这个框架有一个简单的认识了吧!

Fork/Join框架是Java1.7开始提供的一个并行执行框架,结合上面的讲述,再结合Fork/Join的字面意思我们可以知道,先Fork(分叉),再结合!


二丶工作窃取算法

想必只要提到Fork/Join框架,都要提到工作窃取算法,工作窃取是指在分别完成分配的事情时,如果工作结束早的线程可以窃取(帮助)其他线程来完成工作,最终达到总的任务快速完成的目的,就像老板布置一样任务,项目经理将这个任务分给甲乙丙三个程序员来完成这样事情,甲比较厉害,或者分配的事情比较少,他完成以后他主动帮助乙来完成工作。

如果这样子来完成任务,那么怎么分配任务就是一个问题了:

首先将任务分为互不依赖的子任务,将这些子任务放到不同的队列里,并安排一个线程来处理这些任务,线程和队列(一般使用双端队列)一一对应,当有线程完成时,就会去其他队列里窃取任务,但是不同的是,窃取任务是从队列的尾端窃取,防止窃取过程中发生线程竞争。

优点:充分利用并行计算,提高运算效率。

缺点:在某些情况下还是会发生竞争,例如当队列中只剩下一个任务,两个线程进行竞争。


三丶Fork/Join框架的设计思想

理解了Fork/Join框架的设计思想后,我们理解这个框架就会变的非常容易,我们用几行伪代码来直观的告诉你什么是Fork/Join思想:

if(任务足够小){
进行计算;
}else{
将任务分为两个部分;
结合两个子任务;
}

四丶JDK实现Fork/Join框架

(1)JDk为Fork/Join框架提供了很好的支持,我们想要用这个算法首先得创建一个Fork/Join任务,在JDK中这个任务就叫做:ForJoinTask,只要继承这个类就可以创建一个任务类,但是实际使用中并不是直接继承ForkJoinTask类,而是继承它的子类,它有两个子类,分别是RecursiveAction和RecursiveTask,它们之间的区别是是否返回任务结果,前者用于没有返回结果的任务,后者用于有返回结果的任务。

(2)有了Fork/Join任务后还需要执行任务,JDK提供了ForkJoinPool来执行。

(3)下面我们来看一个用ForkJoin框架来进行累加计算例子:

//继承RecursiveTask,用于与结果返回的任务
public class CountTask extends RecursiveTask<Integer>{
//阈值为2,当小于等于这个值的时候进行计算
private static final int THRESHOLD = 2; //累加的起始值
private int start;
private int end; public CountTask(int start,int end){
this.start = start;
this.end = end;
} protected Integer compute() {
int sum = 0;
boolean canCompute = (end-start) <= THRESHOLD;
//如果false,表示end-start大于2,还要进行分割
if(canCompute) {
for(int i = start;i<=end; i++ ) {
sum += i;
}
}
else {
int mid = (end + start) / 2;
//分为两个任务
CountTask leftTask = new CountTask(start,mid);
CountTask rightTask = new CountTask(mid + 1,end);
//执行子任务
leftTask.fork();
rightTask.fork();
//等待子任务完成
int leftResult = leftTask.join();
int rightResult = rightTask.join();
//合并子任务
sum = leftResult+rightResult; }
return sum;
} public static void main(String[] args) {
//创建ForkJoinPool 对象执行任务
ForkJoinPool forkJoinPool = new ForkJoinPool();
//计算1-5累加
CountTask task = new CountTask(1,5);
//执行
Future<Integer> result = forkJoinPool.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

五丶ForkJoin框架的实现原理

在看到例子后我的第一个想法是,这个框架用到了递归,这和我之前看到的归并排序有点类似,那我们来看看框架到底是怎么实现的:

ForkJoinPool由ForkJoinTask数组和ForkJoinWorkThread数组组成。ForkJoinTask负责将存储的任务提交给ForkJoinPool,ForkJoinWorkThread负责处理这些任务。

但是我简单的翻了下源码,发现ForkJoinPool里面已经没有ForkJoinWorkThread数组了,取而代之的是ForkJoinWorkThread工厂(博主Java8)。

既然是实现原理,就从它的核心方法开始看起:

5.1submit方法:

一个任务执行的开端是submit方法,即将任务提交给ForkJoinPool,让它唤醒一个ForkJoinWorkThread线程来执行这个任务:

public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
//任务为空抛异常
if (task == null)
throw new NullPointerException();
//调用externalPush方法,这个我们往下看,看看它玩啥花样
externalPush(task);
return task;
}

final void externalPush(ForkJoinTask<?> task) {
//工作队列,也就是之前介绍框架时所提到的偷窃队列
//创建队列数组的个数和线程数一致
WorkQueue[] ws; WorkQueue q; int m;
//getProbe用来为当前线程生成一个不强制初始化的取样值
int r = ThreadLocalRandom.getProbe();
//runState为ForkJoinPool 的volatile字段,表示锁的状态
int rs = runState;
//workQueues为ForkJoinPool 的数组volatile类型字段
//当这个队列不为空(如果为空肯定是要初始化一个队列的)
//并且队列长度大于等于0
//额...后面原谅我看不懂了
//但是大概的意思应该是要调用我们自定义的task对象的方法,对这个任务进行分割
if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
(q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
U.compareAndSwapInt(q, QLOCK, 0, 1)) {
ForkJoinTask<?>[] a; int am, n, s;
if ((a = q.array) != null &&
(am = a.length - 1) > (n = (s = q.top) - q.base)) {
int j = ((am & s) << ASHIFT) + ABASE;
//将task放入ForkJoinTask数组中
U.putOrderedObject(a, j, task);
U.putOrderedInt(q, QTOP, s + 1);
U.putIntVolatile(q, QLOCK, 0);
//signalWork是用来创建线程来执行任务
if (n <= 1)
signalWork(ws, q);
return;
}
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
externalSubmit(task);
}

5.2fork方法:

我参考过并发编程的艺术,Java7中的fork方法和Java8的fork方法有点小不同!

以下代码是Java8的:

 public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}

我们也说了这个fork方法是由submit方法触发的,且submit方法里面唤醒过ForkJoinWorkerThread来处理队列中的任务,所以通过push方法将当前分割后任务放到队列中,同时调用signalWork唤醒线程对分割后的线程进行处理。

  final void push(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; ForkJoinPool p;
int b = base, s = top, n;
if ((a = array) != null) { // ignore if queue removed
int m = a.length - 1; // fenced write for task visibility
U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
U.putOrderedInt(this, QTOP, s + 1);
if ((n = s - b) <= 1) {
if ((p = pool) != null)
p.signalWork(p.workQueues, this);
}
else if (n >= m)
growArray();
}
}

我对这个框架的理解是这样的(因为网上关于1.8的fork/join框架介绍比较少所以博主水平有限,理解可能有偏差):

1)submit提交task

2)将task暂存到ForkJoinTask数组中

3)调用signalWork方法创建或唤醒线程来执行ForkJoinTask数组中的任务

4)线程拿到任务后调用fork方法

5)fork方法将任务再分割,放到ForkJoinTask数组中(分割前的总任务清 除),然后再唤醒线程来处理ForkJoinTask数组中的任务(唤醒线程的个数取决任务个数)。

6)fork后通过调用join方法将结果合并。

7)任务继续分割,直到不能再分割,然后各个线程进行计算。

8)像递归一样,将数据合并并返回,最终返回到submit方法中。

以上就是博主读fork/join框架的理解,可能偏差很大,大的离谱,如果有大神看见还请赐教!

分析完上面的,博主还是不放心,那就断点走起吧:



上图是我们要执行的主程序(完整程序上面有),第一行和第二行没什么新建一些对象,当执行到Future<Integer> result = forkJoinPool.submit(task);时我们看看都有哪些变量:

这个时候还没啥迹象,且result都没有值,只有这段代码执行结束才能看出点啥Future<Integer> result = forkJoinPool.submit(task);

看看最终形态:

额,告诉大家一个不幸的消息,我没看出啥~(滑稽脸.jpg)

此次分析到此结束,有空再来拜读源码!

恳请有大牛看见了此篇文章指点一下,万谢!!!

2018.4.13 17:19

浅析Java的Frok/Join框架的更多相关文章

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

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

  2. Java 7 Fork/Join 框架

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

  3. Java并发——Fork/Join框架与ForkJoinPool

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

  4. Java使用Fork/Join框架来并行执行任务

    现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机.多核处理器已被广泛应用.在未来,处理器的核心数将会发展的越来越多. 虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这 ...

  5. 我的Java开发学习之旅------>Java使用Fork/Join框架来并行执行任务

    现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机.多核处理器已被广泛应用.在未来,处理器的核心数将会发展的越来越多. 虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这 ...

  6. Java Fork/Join 框架

    简介 从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果. 这种思想和MapReduce很像 ...

  7. Java并发编程(07):Fork/Join框架机制详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.Fork/Join框架 Java提供Fork/Join框架用于并行执行任务,核心的思想就是将一个大任务切分成多个小任务,然后汇总每个小任务 ...

  8. Fork/Join框架详解

    Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.Fork/Join框架要完成两件事情: 1.任务分 ...

  9. 初步了解Fork/Join框架

    框架介绍 Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个子任务,最终汇总每个子任务的执行结果以得到大任务结果的框架.Fork/Join框架要完成两件事 ...

随机推荐

  1. shiro(二)自定义realm,模拟数据库查询验证

    自定义一个realm类,实现realm接口 package com; import org.apache.shiro.authc.*; import org.apache.shiro.realm.Re ...

  2. 排序算法Java实现(冒泡排序)

    算法描述:对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换位置,进行一轮比较和交换后,n个记录中的最大记录将位于第n位:然后对前(n-1)个记录进行 ...

  3. 去除Vue在WebStorm中报命名空间的错误

    Preferences -> Editor -> Inspections找到XML,把 Unbound XML namespace prefix的勾去掉

  4. Java异常处理认识

    什么是异常? 就是在编程过程中或者程序运行过程中出现的一些意外. 什么是异常处理? 就是提前编写程序处理可能发生的意外. 具体的过程是怎样的? 知己知彼方能百战百胜.Java中的异常是用对象来表示的, ...

  5. 爬虫(scrapy中调试文件)

    在项目setting同级目录下创建py文件,代码如下: from scrapy.cmdline import execute import sys import os sys.path.append( ...

  6. Spring MVC核心技术

    目录 异常处理 类型转换器 数据验证 文件上传与下载 拦截器 异常处理 Spring MVC中, 系统的DAO, Service, Controller层出现异常, 均通过throw Exceptio ...

  7. 动态控制jQuery easyui datagrid工具栏显示隐藏

    //隐藏第一个按钮 $('div.datagrid-toolbar a').eq(0).hide(); //隐藏第一条分隔线 $('div.datagrid-toolbar div').eq(0).h ...

  8. 论C++的智能指针

    一.简介   参考这篇博客,并且根据<C++ Primer>中相关知识,我总结了C++关于智能指针方面的内容.   为了解决内存泄漏的问题,便出现了智能指针.STL提供的智能指针有:aut ...

  9. 敏捷冲刺每日报告三(Java-Team)

    第三天报告(10.27  周五) 团队:Java-Team 成员: 章辉宇(284) 吴政楠(286) 陈阳(PM:288) 韩华颂(142) 胡志权(143) github地址:https://gi ...

  10. JAVA使用和操作properties文件

    java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式,在properti ...