1、Fork-Join
Java下多线程的开发我们可以自己启用多线程、线程池,还可以使用ForkJoin。 ForkJoin 可以让我们不去了解诸如 Thread、Runnable 等相关的知识,只要遵循 ForkJoin 的开发模式,就可以写出很好的多线程并发程序。
2、Fork-Join体现了分而治之
什么是分而治之?一种设计思想、策略。十大计算机经典算法:快速排序、堆排序、归并排序、二分查找、线性查找、深度优先、广度优先、Dijktra、动态规划、朴素贝叶斯分类。其中有三个就属于分而治之的设计思想,分别是快速排序、归并排序、二分查找。
分而治之的设计思想是:将一个难以解决的问题,拆分成一些规模较小的问题,以便各个击破,达到分而治之的目的。
3、Fork-Join原理
在必要的情况下,将一个大任务,进行拆分(Fork)成若干个小任务,直到不可再拆时,将一个个小任务运算结果进行汇总(Join),最终达到想要的结果,如下图所示。
4、Fork-Join实战
使用 ForkJoin 框架,必须首先创建一个 ForkJoin 任务。它提供在任务中执行Fork 和 Join 的操作机制,通常我们不直接继承 ForkjoinTask 类,只需要直接继承其子类。
- RecursiveAction,用于没有返回结果的任务
- RecursiveTask,用于有返回值的任务
Task 要通过 ForkJoinPool 来执行,使用 invoke或者submit或者execute 提交,三者的区别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码; submit和execute 是异步执行。 join()和 get() 方法当任务完成的时候返回计算结果。
任务继承RecursiveAction或者RecursiveTask类,重写父类的compute()方法,我们自己的业务写在compute方法里即可,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll 方法时,又会进入 compute 方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用 join 方法会等待子任务执行完并得到其结果。
- Fork/Join的同步用法同时演示返回结果值:统计整形数组中所有元素的和
package cn.lspj.ch2.forkjoin.sum;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 类说明:统计整形数组中所有元素的和
*/
public class SumToForkJoin extends RecursiveTask<Integer> {
//数组长度
public static final int ARRAY_LENGTH = 4000;
/*阈值*/
private final static int THRESHOLD = ARRAY_LENGTH / 10;
private int[] array;
private int fromIndex;
private int toIndex;
public SumToForkJoin(int[] array, int fromIndex, int toIndex) {
this.array = array;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
@Override
protected Integer compute() {
// 判断任务的大小是否合适,当数组长度小于设定的某个阈值时,求该数组的和
if (toIndex - fromIndex < THRESHOLD) {
int count = 0;
for (int i = fromIndex; i < toIndex; i++) {
count += array[i];
}
return count;
}
// 数组长度不满足设定的阈值,继续拆分数组
int mid = (fromIndex + toIndex) / 2;
SumToForkJoin left = new SumToForkJoin(array, fromIndex, mid);
SumToForkJoin right = new SumToForkJoin(array, mid + 1, toIndex);
// 调用invokeAll方法继续执行compute方法
invokeAll(left, right);
return left.join() + right.join();
}
/**
* 随机生成一个数组
*
* @return
*/
private static int[] makeArray() {
//new一个随机数发生器
Random r = new Random();
int[] result = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) {
//用随机数填充数组
result[i] = r.nextInt(ARRAY_LENGTH * 3);
}
return result;
}
public static void main(String[] args) {
// new 出一个pool实例,用来执行task任务
ForkJoinPool pool = new ForkJoinPool();
// 生成一个随机值的数组
int[] array = makeArray();
// new 出一个task任务,交给pool去执行
SumToForkJoin task = new SumToForkJoin(makeArray(), 0, array.length);
// 调用pool的invoke方法,同步执行
pool.invoke(task);
// 调用task的join方法,获取数组求和后的值,并输出
System.out.println("count=" + task.join());
}
}
执行结果如下:
- Fork/Join的异步用法同时演示不要求返回值:遍历指定目录(含子目录)寻找指定类型文件
package cn.lspj.ch2.forkjoin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/**
*类说明:遍历指定目录(含子目录)找寻指定类型文件
*/
public class FindDirsFilesToForkJoin extends RecursiveAction {
private File path;
public FindDirsFilesToForkJoin(File path) {
this.path = path;
}
@Override
protected void compute() {
List<FindDirsFilesToForkJoin> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if (files!=null){
for (File file : files) {
if (file.isDirectory()) {
// 对每个子目录都新建一个子任务。
subTasks.add(new FindDirsFilesToForkJoin(file));
} else {
// 遇到文件,检查。
if (file.getAbsolutePath().endsWith("txt")){
System.out.println("文件:" + file.getAbsolutePath());
}
}
}
if (!subTasks.isEmpty()) {
// 在当前的 ForkJoinPool 上调度所有的子任务。
for (FindDirsFilesToForkJoin subTask : invokeAll(subTasks)) {
subTask.join();
}
}
}
}
public static void main(String [] args){
try {
// 用一个 ForkJoinPool 实例调度总任务
ForkJoinPool pool = new ForkJoinPool();
FindDirsFilesToForkJoin task = new FindDirsFilesToForkJoin(new File("F:/Program Files"));
/*异步提交*/
pool.execute(task);
/*主线程做自己的业务工作*/
System.out.println("Task is Running......");
Thread.sleep(1);
int otherWork = 0;
for(int i=0;i<100;i++){
otherWork = otherWork+i;
}
System.out.println("Main Thread done sth......,otherWork="
+otherWork);
task.join();//阻塞方法
System.out.println("Task end");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行结果如下:
从执行结果中不难看出,使用execute方法异步调用执行task,主线程不会等待子线程先执行,而是继续完成主线程的业务工作。
备注:博主微信公众号,不定期更新文章,欢迎扫码关注。