并发编程之:ForkJoin
大家好,我是小黑,一个在互联网苟且偷生的农民工。
在JDK1.7中引入了一种新的Fork/Join线程池,它可以将一个大的任务拆分成多个小的任务并行执行并汇总执行结果。
Fork/Join采用的是分而治之的基本思想,分而治之就是将一个复杂的任务,按照规定的阈值划分成多个简单的小任务,然后将这些小任务的结果再进行汇总返回,得到最终的任务。
分治法
分治法是计算机领域常用的算法中的其中一个,主要思想就是将将一个规模为N的问题,分解成K个规模较小的子问题,这些子问题相互独立且与原问题性质相同;求解出子问题的解,合并得到原问题的解。
解决问题的思路
- 分割原问题;
- 求解子问题;
- 合并子问题的解为原问题的解。
使用场景
二分查找,阶乘计算,归并排序,堆排序、快速排序、傅里叶变换都用了分治法的思想。
ForkJoin并行处理框架
在JDK1.7中推出的ForkJoinPool线程池,主要用于ForkJoinTask
任务的执行,ForkJoinTask
是一个类似线程的实体,但是比普通线程更轻量。
我们来使用ForkJoin框架完成以下1-10亿求和的代码。
public class ForkJoinMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> rootTask = forkJoinPool.submit(new SumForkJoinTask(1L, 10_0000_0000L));
System.out.println("计算结果:" + rootTask.get());
}
}
class SumForkJoinTask extends RecursiveTask<Long> {
private final Long min;
private final Long max;
private Long threshold = 1000L;
public SumForkJoinTask(Long min, Long max) {
this.min = min;
this.max = max;
}
@Override
protected Long compute() {
// 小于阈值时直接计算
if ((max - min) <= threshold) {
long sum = 0;
for (long i = min; i < max; i++) {
sum = sum + i;
}
return sum;
}
// 拆分成小任务
long middle = (max + min) >>> 1;
SumForkJoinTask leftTask = new SumForkJoinTask(min, middle);
leftTask.fork();
SumForkJoinTask rightTask = new SumForkJoinTask(middle, max);
rightTask.fork();
// 汇总结果
return leftTask.join() + rightTask.join();
}
}
上述代码逻辑可通过下图更加直观的理解。
ForkJoin框架实现
在ForkJoin框架中重要的一些接口和类如下图所示。
ForkJoinPool
ForkJoinPool
是用于运行ForkJoinTasks
的线程池,实现了Executor
接口。
可以通过new ForkJoinPool()
直接创建ForkJoinPool
对象。
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false);
}
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode){
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
通过查看构造方法源码我们可以发现,在创建ForkJoinPool
时,有以下4个参数:
- parallelism:期望并发数。默认会使用
Runtime.getRuntime().availableProcessors()
的值 - factory:创建
ForkJoin
工作线程的工厂,默认为defaultForkJoinWorkerThreadFactory
- handler:执行任务时遇到不可恢复的错误时的处理程序,默认为
null
- asyncMode:工作线程获取任务使用FIFO模式还是LIFO模式,默认为LIFO
ForkJoinTask
ForkJoinTask
是一个对于在ForkJoinPool
中运行任务的抽象类定义。
可以通过少量的线程处理大量任务和子任务,ForkJoinTask
实现了Future
接口。主要通过fork()
方法安排异步任务执行,通过join()
方法等待任务执行的结果。
想要使用ForkJoinTask
通过少量的线程处理大量任务,需要接受一些限制。
- 拆分的任务中避免同步方法或同步代码块;
- 在细分的任务中避免执行阻塞I/O操作,理想情况下基于完全独立于其他正在运行的任务访问的变量;
- 不允许在细分任务中抛出受检异常。
因为ForkJoinTask
是抽象类不能被实例化,所以在使用时JDK为我们提供了三种特定类型的ForkJoinTask父类供我们自定义时继承使用。
- RecursiveAction:子任务不返回结果
- RecursiveTask:子任务返回结果
- CountedCompleter:在任务完成执行后会触发执行
ForkJoinWorkerThread
ForkJoinPool
中用于执行ForkJoinTask
的线程。
ForkJoinPool
既然实现了Executor
接口,那么它和我们常用的ThreadPoolExecutor
之前又有什么差异呢?
如果们使用ThreadPoolExecutor
来完成分治法的逻辑,那么每个子任务都需要创建一个线程,当子任务的数量很大的情况下,可能会达到上万个,那么使用ThreadPoolExecutor
创建出上万个线程,这显然是不可行、不合理的;
而ForkJoinPool
在处理任务时,并不会按照任务开启线程,只会按照指定的期望并行数量创建线程。在每个线程工作时,如果需要继续拆分子任务,则会将当前任务放入ForkJoinWorkerThread
的任务队列中,递归处理直到最外层的任务。
工作窃取算法
ForkJoinPool
的各个工作线程都会维护一个各自的任务队列,减少线程之间对于任务的竞争;
每个线程都会先保证将自己队列中的任务执行完,当自己的任务执行完之后,会去看其他线程的任务队列中是否有未处理完的任务,如果有则会帮助其他线程执行;
为了减少在帮助其他线程执行任务时发生竞争,会使用双端队列来存放任务,被窃取的任务只会从队列的头部获取任务,而正常处理的线程每次都是从队列的尾部获取任务。
优点
充分利用了线程资源,避免资源的浪费,并且减少了线程间的竞争。
缺点
需要给每个线程开辟一个队列空间;在工作队列中只有一个任务时同样会存在线程竞争。
最后
如果觉得文章对你有点帮助,不妨扫码点个关注。我是小黑,下期见~
并发编程之:ForkJoin的更多相关文章
- 并发编程之 Fork-Join 分而治之框架
前言 "分而治之" 一直是一个有效的处理大量数据的方法.著名的 MapReduce 也是采取了分而治之的思想.简单来说,就是如果你要处理1000个数据,但是你并不具备处理1000个 ...
- [转载]并发编程之Operation Queue和GCD
并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- 并发编程之wait()、notify()
前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- python并发编程之Queue线程、进程、协程通信(五)
单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python并发编程之multiprocessing进程(二)
python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...
随机推荐
- Android开发三年,面了八家公司终于腾讯上岸,含泪整理面经
在下2017年毕业,目前从事android开发工作已经3年多了,前段时间刚完成一次跳槽,面试了几家公司,将一些面试经验分享给大家,希望对大家有所帮助. 写在前面 这次跳槽总共是面了8家,(2小,4中, ...
- 『Java』StringBuilder类使用方法
String类存在的问题 String类的底层是一个被final修饰的byte[],不能改变. 为了解决以上问题,可以使用java.lang.StringBuilder类. StringBuilder ...
- Promise教程及用法
目录 1,介绍 2,特点 3,缺点 4,基本用法 5,then 6,catch 7,finally 8,all() 9,race() 10,allSettled() 11,any() 12,现有对象转 ...
- WPF 图表控件之曲线绘制与移动
目的:绘制简单轻量级的曲线视图 二.实现效果: 1,绘制标准基准线 2,可拖动 三.用到控件 1,Canvas 2,Ellipse XAML代码: <Canvas Background=&quo ...
- LNMP 方式部署 zabbix 5.0
文章链接 Zabbix 5.0 LTS新增功能 新版本附带了可用性,安全性和完整性方面的重大改进列表.Zabbix团队遵循的主要策略是使Zabbix尽可能可用.Zabbix是一种开源,免费的监视解决方 ...
- noip模拟8
T1 星际旅行 题目描述 一个图存在欧拉路的条件是有\(2/0\)个点有奇数个出度,把一条无向边拆成两条,所以可以选择拆两个自环.一个自环一条边.连接同一个点的边. 先判断图是否是边联通,不联通则输出 ...
- mac Charles抓包
手机配置http代理 1.配置iPhone或Android 的wifi配置.首先保证Mac电脑和手机是在同一个局域网内. 2.设置手机wifi配置,在HTTP代理中选择手选代理,服务器填写Mac的IP ...
- 【权限管理】Apache Shiro和Spring Security的对比
一.Shiro简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Secu ...
- linux下静态库的制作
在我们编写软件的过程当中,少不了需要使用别人的库函数.因为大家知道,软件是一个协作的工程.作为个人来讲,你不可能一个人完成所有的工作.另外,网络上一些优秀的开源库已经被业内广泛接受,我们也没有必要把 ...
- 十四:Servlet3.0的动态
动态的创建是为了简化配置文件的.对于我们创建的servlet,filter和listener后可以使用.这也是注解的另外一种替代方式. package com.hotusm.dynamic; impo ...