Fork Join 体现了分而治之

什么是分而治之?

  规模为N的问题,如果N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解

Fork Join 框架:

  就是在必要的情况下,将一个大任务,进行拆分(fork)成若干了小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join汇总

Fork Join的另一大特点:工作密取

什么是工作密取?

  就是在按指定阈值拆分后,的多个线程,如果线程A的任务执行的比较快,获得到的CPU时间片比较多,那么在他执行完毕后,就会从未执行完毕的线程的任务中的尾部,进行任务窃取,任务完成后再把结果放回去,不会造成任务竞争,因为自身执行线程的任务是从头部开始获取的,而空闲的线程是从尾部窃取的.

Fork Join使用的标准范式

在使用的过程中我们是无法直接new 一个ForkJoinTask类的,他是一个抽象类,但是他提供了两个子类,RecursiveTask和ResursiveAction两个子抽象类.我们使用的时候,如果需要有返回值,我们就继承RecursiveTask,如果不需要返回值我们就继承RecursiveAction

Fork Join实战

  Fork Join的同步用法同时演示返回结果值:统计整数数组中所有元素的和

先创建一个工具类用于制作整数数组

package org.dance.day2.forkjoin.sum;

import java.util.Random;

/**
* 数组制作类
* @author ZYGisComputer
*/
public class MarkArray { public static final int ARRAY_LENGTH = 4000; /**
* int数组生成器
* @return int数组
*/
public static int[] markArray(){ Random random = new Random(); int[] array = new int[ARRAY_LENGTH]; for (int i = 0; i < ARRAY_LENGTH; i++) {
array[i] = random.nextInt(ARRAY_LENGTH*3);
} return array; } }

然后创建一个单线程的求和类,用于和多线程的对比

package org.dance.day2.forkjoin.sum;

import org.dance.tools.SleepTools;

/**
* 单线程实现求和
* @author ZYGisComputer
*/
public class SumNormal { public static void main(String[] args) {
int count = 0; // 获取数组
int[] src = MarkArray.markArray(); long l = System.currentTimeMillis(); for (int i = 0; i < src.length; i++) {
// 执行一毫秒的休眠
SleepTools.ms(1);
count += src[i];
} System.out.println("The count is "+count+" spend time "+(System.currentTimeMillis() - l));
} }

使用继承RecursiveTask的ForkJoin框架类,完成多线程的求和计算

package org.dance.day2.forkjoin.sum;

import org.dance.tools.SleepTools;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask; /**
* 使用ForkJoin框架实现求和
* @author ZYGisComputer
*/
public class SumArray { /**
* 因为需要返回值所以继承RecursiveTask类
* 因为计算的是整型,所以泛型是Integer
*/
private static class SumTask extends RecursiveTask<Integer> { // 计算阈值
private final static int THRESHOLD = MarkArray.ARRAY_LENGTH/10; // 源数组
private int[] src; // 开始坐标
private int fromIndex; // 结束坐标
private int toIndex; /**
* 通过创建时传入
* @param src 元素组
* @param fromIndex 开始坐标
* @param toIndex 结束坐标
*/
public SumTask(int[] src, int fromIndex, int toIndex) {
this.src = src;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
} /**
* 覆盖执行方法
* @return 整型
*/
@Override
protected Integer compute() {
// 如果 结束下标减去开始下标小于阈值的时候,那么任务就可以开始执行了
if( toIndex - fromIndex < THRESHOLD ){
int count = 0;
// 从开始下标开始循环,循环到结束下标
for (int i = fromIndex; i < toIndex; i++) {
// 休眠1毫秒
SleepTools.ms(1);
count += src[i];
}
return count;
}else{
// 大于阈值 继续拆分任务
// 从formIndex---------------------->到toIndex
// 计算中间值,从formIndex----------计算mid------------>到toIndex
int mid = (fromIndex + toIndex) / 2;
// 左侧任务 从formIndex------------>到mid结束
SumTask left = new SumTask(src, fromIndex, mid);
// 右侧任务 从mid+1开始------------->到toIndex结束
SumTask right = new SumTask(src, mid+1,toIndex);
// 调用任务
invokeAll(left,right);
// 获取结果
return left.join() + right.join();
}
}
} public static void main(String[] args) { // 创建ForkJoin任务池
ForkJoinPool forkJoinPool = new ForkJoinPool(); // 制作源数组
int[] src = MarkArray.markArray(); long l = System.currentTimeMillis(); // 创建一个任务 下标因为从0开始所以结束下标需要-1
SumTask sumTask = new SumTask(src, 0, src.length - 1); // 提交同步任务
Integer invoke = forkJoinPool.invoke(sumTask); // 无论是接收invoke方法的返回值还是调用任务的Join方法都可以获取到结果值
System.out.println("The count is "+invoke+" spend time "+(System.currentTimeMillis() - l));
System.out.println("The count is "+sumTask.join()+" spend time "+(System.currentTimeMillis() - l)); } }

运行结果对比:

现在是4000大小的数组,每次循环休眠1毫秒

单线程执行的结果:

The count is 23751855 spend time 5395

多线程执行的结果:

The count is 23387745 spend time 1487
The count is 23387745 spend time 1487

结果对比多线程比单线程快大概3倍的时间

接下来我们去掉休眠时间,再次进行结果对比:

单线程执行结果:

The count is 23460518 spend time 0

多线程执行结果:

The count is 24078313 spend time 3
The count is 24078313 spend time 3

然后我们惊奇的发现,多线程比单线程还要慢,为什么呢,是因为在小数据量的情况下,单线程,执行期间没有花费上下文切换时间,多线程执行期间是需要花费线程之间上下文切换的时间的,每次上下文切换时间之前说过,大概花费20000-50000个时钟周期的,所以3ms大概切换了10次左右,因为我们是4000大小的数组,阈值是除以10,所以启动的应该是10个线程,所以多线程执行会比单线程慢一些,所以说我们在用多线程的时候,就需要考虑线程之间的上下文切花问题,并不一定多线程就一定是好,我们只是看需求,而选择,就像Redis一样设计的时候就是单线程的,但是他的强大,却是比多线程的memcached更加强大,所以说没有肯定的结论,只有适合和不适合.

接下来我们往大调整整型数组的大小

4000调整为1亿,然后对比结果

单线程执行结果:

The count is -331253431 spend time 51

多线程执行结果:

The count is 75277814 spend time 49
The count is 75277814 spend time 50

我们可以发现,所用的执行时间,已经大概一致了

继续调大1亿调整为3亿,继续对比结果

单线程执行结果:

The count is 57724808 spend time 205

多线程执行结果:

The count is 1028352167 spend time 106
The count is 1028352167 spend time 106

现在单线程已经是多线程的执行时间的两倍了,由此可见,当数据量越来越大的时候,单线程的性能往往就会逐渐降低,而多线程的优势就渐渐体现出来了

所谓的同步用法就是在调用

forkJoinPool.invoke(sumTask);

之后主线程就在这里阻塞了,需要等待,执行完成后,主线程才能继续往下执行,接下里我们看异步用法

  Fork Join的异步用法同时演示不要求返回值:遍历指定目录(含子目录)寻找指定类型文件

package org.dance.day2.forkjoin;

import org.dance.tools.SleepTools;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction; /**
* 使用ForkJoin框架实现不定个数的任务执行
* @author ZYGisComputer
*/
public class FindDirsFiles { /**
* 因为搜索文件不需要返回值,所以我们继承RecursiveAction
*/
private static class FindFilesByDirs extends RecursiveAction{ private File path; public FindFilesByDirs(File path) {
this.path = path;
} @Override
protected void compute() { // 创建任务容器
List<FindFilesByDirs> findFilesByDirs = new ArrayList<>(); // 获取文件夹下所有的对象
File[] files = path.listFiles(); if(null!=files){ for (File file : files) {
// 判断是否是文件夹
if (file.isDirectory()){
// 添加到任务容器中
findFilesByDirs.add(new FindFilesByDirs(file));
}else{
// 如果是一个文件,那么检查这个文件是否符合需求
if(file.getAbsolutePath().endsWith(".txt")){
// 如果符合 打印
System.out.println("文件:"+file.getAbsolutePath());
}
}
} // 判断任务容器是否为空
if(!findFilesByDirs.isEmpty()){
// 递交任务组
for (FindFilesByDirs filesByDirs : invokeAll(findFilesByDirs)) {
// 等待子任务执行完成
filesByDirs.join();
} } } }
} public static void main(String[] args) { // 创建ForkJoin池
ForkJoinPool forkJoinPool = new ForkJoinPool(); File path = new File("E:/"); // 创建任务
FindFilesByDirs findFilesByDirs = new FindFilesByDirs(path); // 异步调用 这个方法是没有返回值的
forkJoinPool.execute(findFilesByDirs); System.out.println("Task is Running................");
SleepTools.ms(1); // 在这里做这个只是测试ForkJoin是否为异步,当执行ForkJoin的时候主线程是否继续执行
int otherWork = 0;
for (int i = 0; i < 100; i++) {
otherWork += i;
}
System.out.println("Main thread done sth.......,otherWork:"+otherWork); // 如果是有返回值的话,可以获取,当然这个join方法是一个阻塞式的,因为主线程执行的太快了,ForkJoin还没执行完成主线程就死亡了,所以在这里调用一下阻塞,等待ForkJoin执行完成
findFilesByDirs.join(); System.out.println("Thread end!"); } }

执行结果:

Task is Running................
Main thread done sth.......,otherWork:4950
文件:E:\dance\activiti-ruoyi\RuoYi-Process\ruoyi-admin\src\main\resources\static\file\rml.txt
文件:E:\dance\activiti-ruoyi\RuoYi-Process\ruoyi-admin\target\classes\banner.txt
文件:E:\dance\activiti-ruoyi\RuoYi-Process\ruoyi-admin\target\classes\static\ajax\libs\jquery-ztree\3.5\log v3.x.txt
文件:E:\dance\activiti-ruoyi\RuoYi-Process\ruoyi-admin\target\classes\static\file\rml.txt
........................
Thread end!

从执行结果中可以看到,主线程的执行时在ForkJoin执行之前就执行了,但是代码中却是在ForkJoin执行之后执行的,所以说这是异步的,线程是并行执行的,异步执行只能通过调用任务线程的Join方法获取返回值,execute方法是没有返回值的

作者:彼岸舞

时间:2020\09\18

内容关于:并发编程

本文来源于网络,只做技术分享,一概不负任何责任

Fork Join 并发任务执行框架的更多相关文章

  1. fork/join并发编程

    Fork & Join 的具体含义 Fork 一词的原始含义是吃饭用的叉子,也有分叉的意思.在Linux 平台中,函数 fork()用来创建子进程,使得系统进程可以多一个执行分支.在 Java ...

  2. 谈谈fork/join实现原理

    害,又是一个炒冷饭的时间.fork/join是在jdk1.7中出现的一个并发工作包,其特点是可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出.从而达到多 ...

  3. fork/join 全面剖析

    fork/join作为一个并发框架在jdk7的时候就加入到了我们的java并发包java.util.concurrent中,并且在java 8 的lambda并行流中充当着底层框架的角色.这样一个优秀 ...

  4. FutureTask、Fork/Join、 BlockingQueue

    我们之前学习创建线程有Thread和Runnable两种方式,但是两种方式都无法获得执行的结果. 而Callable和Future在任务完成后得到结果.   Future是一个接口,表示一个任务的周期 ...

  5. 转:聊聊并发(八)——Fork/Join框架介绍

    1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过 ...

  6. 并发编程学习笔记(12)----Fork/Join框架

    1. Fork/Join 的概念 Fork指的是将系统进程分成多个执行分支(线程),Join即是等待,当fork()方法创建了多个线程之后,需要等待这些分支执行完毕之后,才能得到最终的结果,因此joi ...

  7. Java 并发编程 -- Fork/Join 框架

    概述 Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.下图是网上流传的 Fork Join 的 ...

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

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

  9. ☕【Java技术指南】「并发编程专题」Fork/Join框架基本使用和原理探究(基础篇)

    前提概述 Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行. 我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一 ...

随机推荐

  1. SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. js使用html2canvas 生成图片然后下载

    1:html2canvas官网 首先去官网把这个JS下载下来 <!DOCTYPE html> <html lang="en"> <head> & ...

  3. windows 服务端 狼人杀 发牌器 开发完成 待继续开发其他服务

    开发工具: python2.7 eric4 成果链接地址 https://wws.lanzous.com/iPCDTfnuoif

  4. 《Java从入门到失业》第二章:Java环境(二):JDK、JRE、JVM

    2.2JDK.JRE.JVM 在JDK的安装目录中,我们发现有一个目录jre(其实如果是下一步下一步安装的,在和JDK安装目录同级目录下,还会有一个jre目录).初学Java的同学,有时候搞不清楚这3 ...

  5. 蒲公英 &#183; JELLY技术周刊 Vol.18 关于 React 那些设计

    蒲公英 · JELLY技术周刊 Vol.18 自 2011 年,Facebook 第一次在 News Feed 上采用了 React 框架,十年来 React 生态中很多好用的功能和工具在诸多设计思想 ...

  6. Jmeter 常用函数(15)- 详解 __StringFromFile

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 从文本文件读取字符串,每次一行 需要注意 ...

  7. Spring注解驱动开发01(组件扫描使用详解)

    使用Spring注解代替XML的方式 以前都是通过xml配bean的方式来完成bean对象放入ioc容器,即使通过@Aotuwire自动装配bean,还是要创建一个xml文件,进行包扫描,显得过于繁琐 ...

  8. golang time包

    1.时间类型 time.Time类型表示时间. func demo() { now := time.Now() //获取当前时间 fmt.Printf("Now:%v\n", no ...

  9. C++数的表示

    二进制B 八进制O 十进制D 十六进制H / 0x十六进制 十进制数转换成R进制数:整数部分除基取余,上右下左:小数部分乘基取整,上左下右.   浮点数的阶用一种称为移码的编码表示方法,方便对阶.阶的 ...

  10. 【小白学AI】XGBoost推导详解与牛顿法

    文章来自微信公众号:[机器学习炼丹术] 目录 1 作者前言 2 树模型概述 3 XGB vs GBDT 3.1 区别1:自带正则项 3.2 区别2:有二阶导数信息 3.3 区别3:列抽样 4 XGB为 ...