JAVA并发工具类---------------(Fork/Join)
Fork/Join
分而治之
将一个大任务分成数个小任务执行,然后将这些小人物执行后的结果进行join汇总;
(假设:你要计算1到1000的总和,你可以把它分成1-100,101-200,......,901-1000几组完成,然后再把这几组的结果相加)
工作窃取
- 有一个较大的任务划分成了10个小任务。
- 这10个小任务在一个大小为2的线程池中执行。
- 线程池中的2个核心线程,每个线程的队列中有5个任务。
- 线程1的任务都很简单,所以它很快就将5个任务执行完毕。
- 线程2的任务都很复杂,当线程1执行完5个任务时,他才执行了3个任务。
- 这时,线程1不会空闲,而且窃取线程2的等待队列中的任务(从末端开始窃取)来执行。
- 当线程2的队列中也没有了任务之后,线程1和线程2才空闲。
(假设:你和同事执行相同的任务,你执行的任务快,但是你的同事执行很慢,你把你的任务执行完成后,你会帮你的同事执行一部分任务,然后再偷偷将完成的任务放在你同事的任务完成列表里面)
ForkJoin的主要类
ForkJoinPool:ForkJoin线程池,实现了ExecutorService接口和工作窃取算法,用于线程调度与管理。
ForkJoinTask:ForkJoin任务,提供了fork()方法和join()方法。通常不直接使用,而是使用以下子类:
RecursiveAction:无返回值的任务,通常用于只fork不join的情形。
RecursiveTask:有返回值的任务,通常用于fork+join的情形。
ForkJoin的使用
一、 创建Task
使用ForkJoin框架,需要创建一个ForkJoin的任务,而ForkJoinTask是一个抽象类,我们不需要去继承ForkJoinTask进行使用。因为ForkJoin框架为我们提供了RecursiveAction和RecursiveTask。我们只需要继承ForkJoin为我们提供的抽象类的其中一个并且实现compute方法。其中RecursiveAction没有返回结果,RecursiveTask执行后是有返回结果,看需使用。
二、使用ForkJoinPool进行执行
task要通过ForkJoinPool来执行,分割的子任务也会添加到当前工作线程的双端队列中,
进入队列的头部。当一个工作线程中没有任务时,会从其他工作线程的队列尾部获取一个任务(工作窃取)。
TASK任务类
package com.qr.fork_join.ListTskDemo; import java.util.List;
import java.util.concurrent.RecursiveTask; //
public class DemoTask extends RecursiveTask<Integer> { //传入的参数
final List<Integer> list; public DemoTask(List<Integer> list) {
this.list = list;
} //需要执行的逻辑
@Override
protected Integer compute() {
//分组条件
if (list.size()<=10){
//分组后需要执行的逻辑--计算总和
int sum=0;
for (Integer integer : list) {
sum+=integer;
}
return sum;
}
// 执行子任务
DemoTask task1=new DemoTask(list.subList(0, list.size() / 2) );
DemoTask task2=new DemoTask( list.subList(list.size() / 2, list.size()) ); //等待任务执行结束合并其结果
task1.fork();
task2.fork();
//也可以使用 invokeAll(task1, task2); // 合并子任务
return task1.join()+task2.join();
}
}
主线程任务类
package com.qr.fork_join.ListTskDemo; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool; public class DemoTest {
public static void main(String[] args) {
long startTime1=System.currentTimeMillis(); //获取开始时间
List<Integer> list=new ArrayList<Integer>();
//正确的返回参数
int sum=0;
for (int i = 0; i <1000 ; i++) {
sum+=i;
list.add(i);
}
long endTime1=System.currentTimeMillis(); //获取开始时间
System.out.println("单线程用时:"+(endTime1-startTime1)+"ms"); long startTime2=System.currentTimeMillis(); //获取开始时间
ForkJoinPool pool=new ForkJoinPool();
DemoTask demoTask=new DemoTask(list);
pool.submit(demoTask);
try {
//使用forkjoin框架返回的参数
System.out.println(demoTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
long endTime2=System.currentTimeMillis(); //获取开始时间
System.out.println("多线程用时:"+(endTime2-startTime2)+"ms");
}
}
执行结果
单线程用时:0ms
499500
多线程用时:76ms
重点注意
需要特别注意的是:
- ForkJoinPool 使用submit 或 invoke 提交的区别:invoke是同步执行,调用之后需要等待任务完成,才能执行后面的代码;submit是异步执行,只有在Future调用get的时候会阻塞。
- 这里继承的是RecursiveTask,还可以继承RecursiveAction。前者适用于有返回值的场景,而后者适合于没有返回值的场景
这一点是最容易忽略的地方,其实这里执行子任务调用fork方法并不是最佳的选择,最佳的选择是invokeAll方法。
leftTask.fork();
rightTask.fork(); 替换为 invokeAll(leftTask, rightTask);
具体说一下原理:对于Fork/Join模式,假如Pool里面线程数量是固定的,那么调用子任务的fork方法相当于A先分工给B,然后A当监工不干活,B去完成A交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用invokeAll相当于A分工给B后,A和B都去完成工作。这样可以更好的利用线程池,缩短执行的时间。
ForkJoinTask
fork 方法
fork()做的工作只有一件事,既是把任务推入当前工作线程的工作队列里。
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;
}
join 方法
join()的工作则复杂得多,也是它可以使得线程免于被阻塞的原因。
检查调用
join()的线程是否是 ForkJoinThread 线程。如果不是(例如 main 线程),则阻塞当前线程,等待任务完成。如果是,则不阻塞。- 查看任务的完成状态,如果已经完成,直接返回结果。
- 如果任务尚未完成,但处于自己的工作队列内,则完成它。
- 如果任务已经被其他的工作线程偷走,则窃取这个小偷的工作队列内的任务(以 FIFO 方式)执行,以期帮助它早日完成预 join 的任务。
- 如果偷走任务的小偷也已经把自己的任务全部做完,正在等待需要 Join 的任务时,则找到小偷的小偷,帮助它完成它的任务。
- 递归地执行第 5 步。
ForkJoinPool
execute方法
异步,不返回结果
invoke方法
同步,返回结果
submit方法
异步,返回结果
JAVA并发工具类---------------(Fork/Join)的更多相关文章
- 25.大白话说java并发工具类-CountDownLatch,CyclicBarrier,Semaphore,Exchanger
1. 倒计时器CountDownLatch 在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join ...
- Java并发工具类 - CountDownLatch
Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http: ...
- 基于AQS实现的Java并发工具类
本文主要介绍一下基于AQS实现的Java并发工具类的作用,然后简单谈一下该工具类的实现原理.其实都是AQS的相关知识,只不过在AQS上包装了一下而已.本文也是基于您在有AQS的相关知识基础上,进行讲解 ...
- Java并发工具类CountDownLatch源码中的例子
Java并发工具类CountDownLatch源码中的例子 实例一 原文描述 /** * <p><b>Sample usage:</b> Here is a pai ...
- java 并发工具类CountDownLatch & CyclicBarrier
一起在java1.5被引入的并发工具类还有CountDownLatch.CyclicBarrier.Semaphore.ConcurrentHashMap和BlockingQueue,它们都存在于ja ...
- JAVA并发工具类---------------(CountDownLatch和CyclicBarrier)
CountDownLatch是什么 CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 闭锁可以延迟线程的进 ...
- 【Java并发工具类】Semaphore
前言 1965年,荷兰计算机科学家Dijkstra提出的信号量机制成为一种高效的进程同步机制.这之后的15年,信号量一直都是并发编程领域的终结者.1980年,管程被提出,成为继信号量之后的在并发编程领 ...
- 【Java并发工具类】Java并发容器
前言 Java并发包有很大一部分都是关于并发容器的.Java在5.0版本之前线程安全的容器称之为同步容器.同步容器实现线程安全的方式:是将每个公有方法都使用synchronized修饰,保证每次只有一 ...
- 【java并发系列】Fork/Join任务(转)
原文链接 当我们需要执行大量的小任务时,有经验的Java开发人员都会采用线程池来高效执行这些小任务.然而,有一种任务,例如,对超过1000万个元素的数组进行排序,这种任务本身可以并发执行,但如何拆解成 ...
随机推荐
- NRF24L01学习
一.工作模式 PWR_UP和PRIM_RX在寄存器0x00中的第0位和第1位. 待机模式I:只是使用晶振工作,CE=0时是拉低? 待机模式II:部分时钟缓冲器处在工作模式.TX FIFO为空并且CE为 ...
- SCP-bzoj-1069
项目编号:bzoj-1069 项目等级:Safe 项目描述: 戳这里 特殊收容措施: 求凸包后在凸包上旋转卡壳.然而复杂度要求较低,故可直接枚举四边形的一条对角线,另两个顶点在凸包上随这条对角线的移动 ...
- BZOJ 3531: [Sdoi2014]旅行(树链剖分+线段树)
传送门 解题思路 以每个颜色为根开一棵权值线段树,下标就是\(dfs\)序,其余都是基本操作,要动态开点. 代码 #include<iostream> #include<cstdio ...
- 【Tomcat】使用Tomcat部署Spring Boot项目生成的jar包
介绍 简单来说,Tomcat是一个免费的,用于Java Web应用以及其它Web应用的一个Web服务器.(简单地概括一下,可能有误) 下载与安装 本文章目标是把Spring Boot Web项目生成的 ...
- hdu6578 2019湖南省赛D题Modulo Nine 经典dp
目录 题目 解析 AC_Code @ 题目 第一题题意是一共有{0,1,2,3}四种数字供选择,问有多少个长度为n的序列满足所有m个条件,每个条件是说区间[L,R]内必须有恰好x个不同的数字. 第二题 ...
- 转:Linux设备树(Device Tree)机制
目录 1. 设备树(Device Tree)基本概念及作用 2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DTB 2.4. Bootloader 3. 设备树中d ...
- HTML5: HTML5 测验
ylbtech-HTML5: HTML5 测验 1.返回顶部 1. HTML5 测验 结果:15/5 1. HTML5 之前的 HTML 版本是什么? 你的回答: HTML 4.01 回答正确! 2. ...
- spring boot找不到或无法加载主类 io.renren.RenrenApplication
spring boot找不到或无法加载主类 io.renren.RenrenApplication 出现问题: spring boot 项目以前一直是好好的,用mvn clean package 打包 ...
- 研究一下phpspider
官方文档 1.下载 官方github下载地址: https://github.com/owner888/phpspider 下载地址可能无法访问,这里提供一个网盘下载地址: 链接: https://p ...
- 如何在原生Android项目里嵌入Cordova
背景: 这段时间在维护一个Cordova混合项目,以前稍微接触过Cordova,也写过简单的纯纯的Cordova的Demo,但是没有尝试过混合原生的Cordova. 在接到项目后比较了一下项目架构和C ...