Fork-join概念图
分而治之:将一个大的任务分成N个小任务同时进行执行,等待所有的小任务执行完成后,进行结果汇总。
工作密取:因为多个小任务执行完成的时间不一样,比如A线程先完成计算的小任务的结果,将结果放到一个指定的队列中,A线程就不需要继续等待其他线程执行完成,A可以继续干其他事情了。
Fork-join使用标准范式
Fork-join进行1-10000累加操作
/*
* 产生整形数组的工具类
*
* */
public class MakeArrays {
public static final int ARRAY_LENGTH = 4000;
public static int[] makeArrays(){
//随机生成
Random random = new Random();
int[] result = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) {
result[i] = random.nextInt(ARRAY_LENGTH*3);
}
return result;
}
}
/*
* 使用方式:自定义一个任务类继承RecursiveTask<返回类型>,重写compute()方法
* 因为累加操作。采用的是二分查找方法,进行累加的,所以有以下几个步骤
* 任务类,定义临界值,开始和结下标,
* 在compute方法中,有两个分支
* 1.满足临界值条件时候,计算业务逻辑代码,返回计算结果
* 2.不满足业务逻辑的时候,继续拆分任务类为多个细小的任务类。加入到invokeALl中
*在main函数中创建一个ForkJoinPool对象,然后ForkJoinPool.pool(任务类)即可执行。
* */
public class ForkJoin1 extends RecursiveTask<Integer> {
//需要统计的数据
private int[] src;
//三个必备的基本属性
//1.临界值----
private static final int THRESHOLD = MakeArrays.ARRAY_LENGTH / 10;
//2.开始统计的下标,
private int fromIndex;
//3.结束的下标
private int toIndex;
public ForkJoin1(int[] src, int fromIndex, int toIndex) {
this.fromIndex = fromIndex;
this.src = src;
this.toIndex = toIndex;
}
//fork-join
@Override
protected Integer compute() {
//如果条件小于临界值的时候,说明不可再分了,做自己业务的逻辑
if (toIndex-fromIndex<THRESHOLD){
int count = 0;
for (int i = fromIndex; i <= toIndex; i++) {
SleepTools.ms(1);
count+=src[i];
}
return count;
}else {
//如果不满足条件,继续拆分(算法自定义),这里就相当于二分查找,不做任何业务逻辑
//fromIndex............mid.............toIndex
int mid = (toIndex+fromIndex)/2;
ForkJoin1 left = new ForkJoin1(src,fromIndex,mid);
ForkJoin1 right = new ForkJoin1(src, mid + 1, toIndex);
//将任务加入到invokeAll
invokeAll(left,right);
//返回左右两边的计算结果
return left.join()+right.join();
}
}
//单线程进行数据累加
public static void singleAddArray(int[] ints) {
int count = 0;
long start = System.currentTimeMillis();
for (int anInt : ints) {
SleepTools.ms(1);
count += anInt;
}
long end = System.currentTimeMillis();
System.out.println("单线程的计算结果耗时:" + (end - start) + "/ms,count=" + count);
}
public static void main(String[] args) {
int[] ints = MakeArrays.makeArrays();
//单线程计算结果
singleAddArray(ints);
//多线程计算结果
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoin1 forkJoin1 = new ForkJoin1(ints, 0, ints.length - 1);
long start = System.currentTimeMillis();
forkJoinPool.invoke(forkJoin1);
long end = System.currentTimeMillis();
System.out.println("fork多线程线程的计算结果耗时:" + (end - start) + "/ms,count=" + forkJoin1.join());
}
}
单线程的计算结果耗时:7307/ms,count=24125977
fork多线程线程的计算结果耗时:1771/ms,count=24125977
可以明显看到,时间比单线程快上许多。但是如果我们把每个计算方法里面的 SleepTools.ms(1);去掉并且ARRAY_LENGTH 长度扩展成4000000。再看下打印的结果
单线程的计算结果耗时:5/ms,count=1027434167
fork多线程线程的计算结果耗时:9/ms,count=1027434167
明显看到单线程此时的计算时间反而比我们的fork多线程计算时间还快,这是为什么呢?我们都知道多线程在使用的时候会频繁的进行上下文切换是需要消耗一定的时间的,但是单线程是基于内存的,舍去了上下文切换的这一步时间,所以导致速度快于多线程。
Fork-join查询E盘下的所有目录
在遍历文件的时候,我们发现目录(文件夹)下面可能是文件也有可能还是目录。但是他们我们的条件是唯一的,就是打印出所有文件名称。因此,将目录下的目录交给Forkjoin任务管理即可。
/*fork-join异步用法,遍历某个盘下的所有文件
*继承RecursiveAction是无返回值
*继承RecursiveTask<Integer>带有返回值
* */
public class ForkJoinSync extends RecursiveAction {
//入口文件目录
private File path;
public ForkJoinSync(File path){
this.path = path;
}
@Override
protected void compute() {
//获取入口目录下的所有文件
File[] files = this.path.listFiles();
//创建一个List集合,用于存放所有的任务(在该业务中,目录下的目录就是待执行的任务。)
List<ForkJoinSync> forkJoinSyncList = new ArrayList<>();
if (files!=null){
//循环所有文件,判断该文件是不是目录。是目录在继续遍历
for (File file : files) {
if (file.isDirectory()){
//是目录,将目录作为task交给forkjoin管理
forkJoinSyncList.add(new ForkJoinSync(file));
}else {
//不是目录。打印出文件
System.out.println(file.getAbsoluteFile());
}
}
//将任务交给invokeAll进行管理。
if (forkJoinSyncList.size()>0){
this.invokeAll(forkJoinSyncList);
}
}
}
public static void main(String[] args) {
File file = new File("E:\\购买的JAVA资料\\学习阶段1\\学习1");
ForkJoinSync forkJoinSync = new ForkJoinSync(file);
System.out.println("-----------我是Main线程准备执行Fork-Join的任务----------------");
//1.创建fork-join任务池
ForkJoinPool forkJoinPool = new ForkJoinPool();
//2.将任务交给forkJoinPool进行执行execute是异步执行,invoke是同步执行
forkJoinPool.execute(forkJoinSync);
System.out.println("-----------我是Main线程Fork-Join的任务执行完毕----------------");
//这里采用睡眠失眠进行main线程阻塞,如果把这个去掉,可以看到结果并未打印出来,因为在main执行完成后,forkjoin可能刚刚初始化成功。
//当然也可以采用forkJoinSync.join()进行线程阻塞
SleepTools.sencod(10);
System.out.println("main---end");
}
}
-----------我是Main线程准备执行Fork-Join的任务----------------
-----------我是Main线程Fork-Join的任务执行完毕----------------
E:\购买的JAVA资料\学习阶段1\学习1\免责声明.txt
E:\购买的JAVA资料\学习阶段1\学习1\面试专题\10.动态代理的几种实现方式及优缺点_recv_.ev4.mp4
E:\购买的JAVA资料\学习阶段1\学习1\设计模式\patterns.rar
...........此处省略打印的文件................
main---end
根据打印日志可以直观的看见,main线程和fork线程是异步执行的
有时候业务场景往往是需要异步计算,然后返回结果的汇总的,这时候就需要继承带有返回值的RecursiveTask类了。
/*fork-join异步用法,遍历某个盘下的所有文件
*继承RecursiveAction是无返回值
*继承RecursiveTask<Integer>带有返回值
* */
public class ForkJoinSync extends RecursiveTask<List<File>> {
//入口文件目录
private File path;
public ForkJoinSync(File path){
this.path = path;
}
@Override
protected List<File> compute() {
//获取入口目录下的所有文件
File[] files = this.path.listFiles();
//创建一个List集合,用于存放所有的任务(在该业务中,目录下的目录就是待执行的任务。)
List<ForkJoinSync> forkJoinSyncList = new ArrayList<>();
//返回打印的文件名称
List<File> listFile= new ArrayList<>();
if (files!=null){
//循环所有文件,判断该文件是不是目录。是目录在继续遍历
for (File file : files) {
if (file.isDirectory()){
//是目录,将目录作为task交给forkjoin管理
forkJoinSyncList.add(new ForkJoinSync(file));
}else {
//不是目录。打印出文件
listFile.add(file.getAbsoluteFile());
}
}
//将任务交给invokeAll进行管理。
if (forkJoinSyncList.size()>0){
this.invokeAll(forkJoinSyncList);
for (ForkJoinSync forkJoinSync : forkJoinSyncList) {
listFile.addAll(forkJoinSync.join());
}
}
}
return listFile;
}
public static void main(String[] args) {
File file = new File("E:\\购买的JAVA资料\\学习阶段1\\学习1");
ForkJoinSync forkJoinSync = new ForkJoinSync(file);
System.out.println("-----------我是Main线程准备执行Fork-Join的任务----------------");
//1.创建fork-join任务池
ForkJoinPool forkJoinPool = new ForkJoinPool();
//2.将任务交给forkJoinPool进行执行execute是异步执行,invoke是同步执行
forkJoinPool.execute(forkJoinSync);
List<File> join = forkJoinSync.join();
for (File file1 : join) {
System.out.println(file1.getAbsoluteFile());
}
System.out.println("-----------我是Main线程Fork-Join的任务执行完毕----------------");
//这里采用睡眠失眠进行main线程阻塞,如果把这个去掉,可以看到结果并未打印出来,因为在main执行完成后,forkjoin可能刚刚初始化成功。
//当然也可以采用forkJoinSync.join()进行线程阻塞
SleepTools.sencod(10);
System.out.println("main---end");
}
}
fork-join遍历list
拆分的任务=ist.size();
/*
* 将每个数组中的值做未key,value是随机数,最终返回Map<>结构。
* {t2028=427, t2029=52, t2022=977, t2023=145, t2020=270, t2021=786, t2026=833, t2027=742, t2024=200, t2025=256}
* */
public class ForkJoin2 {
//获取数据
public List<String> getTableName() {
return Arrays.asList("t2020", "t2021", "t2022", "t2023", "t2024", "t2025", "t2026", "t2027", "t2028", "t2029");
}
public static void main(String[] args) {
ForkJoin2 forkJoin2 = new ForkJoin2();
ForkJoinPool forkJoinPool = new ForkJoinPool();
CalculateNmus calculateNmus = new CalculateNmus(forkJoin2.getTableName());
forkJoinPool.execute(calculateNmus);
long start = System.currentTimeMillis();
Map<String, Object> join = calculateNmus.join();
System.out.println("fork多线程线程的计算结果耗时:" + (System.currentTimeMillis() - start));
System.err.println(join);
}
static class CalculateNmus extends RecursiveTask<Map<String, Object>> {
private List<String> list;
public CalculateNmus(List<String> list) {
this.list = list;
}
@Override
protected Map<String, Object> compute() {
//已经将list拆分完了
Map<String, Object> map = new HashMap<>();
List<CalculateNmus> taskList = new ArrayList<>();
if (list.size() == 1) {
map.put(list.get(0), new Random().nextInt(1000));
} else if (list.size() > 1) {
//开始拆分,创建一个任务集合
for (int i = 0; i < list.size(); i++) {
CalculateNmus calculateNmus = new CalculateNmus(Arrays.asList(list.get(i)));
taskList.add(calculateNmus);
}
//批量执行任务
if (taskList.size() > 0) {
this.invokeAll(taskList);
for (CalculateNmus calculateNmus : taskList) {
map.putAll(calculateNmus.join());
}
}
}
return map;
}
}
}