浅析Java的Frok/Join框架
一丶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框架的更多相关文章
- Java并发——Fork/Join框架
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4631466. ...
- Java 7 Fork/Join 框架
在 Java7引入的诸多新特性中,Fork/Join 框架无疑是重要的一项.JSR166旨在标准化一个实质上可扩展的框架,以将并行计算的通用工具类组织成一个类似java.util中Collection ...
- Java并发——Fork/Join框架与ForkJoinPool
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4631466. ...
- Java使用Fork/Join框架来并行执行任务
现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机.多核处理器已被广泛应用.在未来,处理器的核心数将会发展的越来越多. 虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这 ...
- 我的Java开发学习之旅------>Java使用Fork/Join框架来并行执行任务
现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机.多核处理器已被广泛应用.在未来,处理器的核心数将会发展的越来越多. 虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这 ...
- Java Fork/Join 框架
简介 从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果. 这种思想和MapReduce很像 ...
- Java并发编程(07):Fork/Join框架机制详解
本文源码:GitHub·点这里 || GitEE·点这里 一.Fork/Join框架 Java提供Fork/Join框架用于并行执行任务,核心的思想就是将一个大任务切分成多个小任务,然后汇总每个小任务 ...
- Fork/Join框架详解
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.Fork/Join框架要完成两件事情: 1.任务分 ...
- 初步了解Fork/Join框架
框架介绍 Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个子任务,最终汇总每个子任务的执行结果以得到大任务结果的框架.Fork/Join框架要完成两件事 ...
随机推荐
- linux-2.6.18源码分析笔记---信号
一.相关数据结构及其位置(大致浏览即可,介绍流程时再来仔细看) 1.1 进程描述符struct task_struct所在目录:include\linux\sched.h 关注task_struct中 ...
- 【Django】 rest-framework和RestfulAPI的设计
[rest-framework] 这是一个基于django才能发挥作用的组件,专门用于构造API的. 说到API,之前在其他项目中我也做过一些小API,不过那些都是玩票性质,结构十分简单而且要求的设计 ...
- cisco交换机实现端口聚合
0x00前言: 今天听老师讲端口聚合,为了方便日后复习故此有 了本篇随笔. 0x01准备工具: cisco模拟器 0x02:目录 为什么要用端口聚合? 广播风暴? 扩展:SMTP 0x03正文: 为什 ...
- oracle 11g数据库 DMP还原数据库
-------------------------- jd :表空间 -------------------------- --本地登陆 cmd下直接执行 sqlplus/as sysdba; --修 ...
- 第四次团队作业:社团申请App
概要: 基于上次软件设计本着界面简洁.易于使用的初衷,进行功能的实现,代码位置:https://github.com/LinZezhong/testDemo 第一部分:软件的使用 注册: 登录: 主界 ...
- 敏捷冲刺每日报告——Day3
1.情况简述 Alpha阶段第一次Scrum Meeting 敏捷开发起止时间 2017.10.27 00:00 -- 2017.10.28 00:00 讨论时间地点 2017.10.27晚9:30, ...
- JAVA使用和操作properties文件
java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式,在properti ...
- webview缓存及跳转时截取url地址、监听页面变化
缓存及一些设定 我在做一些项目时,h5做的项目手机浏览器能使用,但是在搬到webview时候不能用,这个时候通过查阅资料,原来是webview没有设定好,包括缓存.缓存大小及路径等等 mWebview ...
- Java并发编程实战 之 线程安全性
1.什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全 ...
- JAVA_SE基础——5.第一个Java程序HelloWorld&注释的应用
配置完JDK&环境变量后,我们就可以开始写程序了,那么程序怎么写呢,用什么工具呢,我建议 为了方便学习,我们最好在一个磁盘下建立一个专门的文件来写java程序,比如就在D盘下建立一个名为&qu ...