概述
从JDK1.7开始,Java提供ForkJoin框架用于并行执行任务,它的核心思想就是分而治之,本文将详细介绍这个框架的使用。
什么是分而治之
分而治之的基本思想是:
将一个规模为N的问题分解为K个规模较小的子问题(K <= N),这些子问题相互独立且与原问题性质相同,求出子问题的解,就可以求出原问题的解。
分而治之也是递归的一种,一般的实现步骤为:
(1)设定阈值K
(2)不断将问题分解(或者说缩小规模),直到符合阈值K。
(3)按问题的要求,判断子问题的解,将子问题的解逐层合并构成原问题的解。
分而治之的重点:
看是否能够发现重复的子问题,能否发现大问题存在的循环子结构,如果发现就把原问题转化为很简单的小问题。
是否能划分步骤(不同步骤不同解决方法),因为单个步骤往往比整个问题解决起来要简单很多。
子问题是否很容易解决。
比如一个规模为n的问题,可以划分为1和 n-1两个部分,其中1是易于解决的。而n-1这个剩余部分可以用相同的划分方式分成1 , n-2两部分;重复这个过程,最终解决所有问题。也可以划分成n/2和n/2 两部分,然后对两个部分继续划分,最终都会成为一个1的简单问题。
常见的二分查找,二叉树等都采用了分而治之的思想。
ForkJoin使用的一般流程
// 1.初始化ForkJoin池
ForkJoinPool pool = new ForkJoinPool();
// 2.自定义任务实现
ForkJoinDemoTask task = new ForkJoinDemoTask();
// 3.执行任务
pool.invoke(task);
// 4.获取结果
task.join()
其中ForkJoinDemoTask是我们需要自定义实现的类,也是ForkJoin的核心。
ForkJoinDemoTask需要实现抽象类RecursiveTask、RecursiveAction、ForkJoinTask。核心方法是compute(),用于处理问题的核心业务和划分拆分子任务。
ForkJoin的同步调用Demo
计算1-1000的数累加,其中单个累加模拟10ms的业务处理,通过传统单线程和ForkJoin分别实现。
public class ForkJoinTest {
/**
* 核心类
*/
public static class ForkJoinTask extends RecursiveTask<Integer> {
// 阈值size
private final static int size = 10;
private int[] src;
private int from;
private int to;
public ForkJoinTask(int[] src, int from, int to) {
this.src = src;
this.from = from;
this.to = to;
}
@Override
protected Integer compute() {
if(to - from <= size) {
// 规模为N的问题,N<阈值,直接解决
int count = 0;
for(int i = from; i <= to; i++) {
try {
// 延时10ms 模拟业务处理
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count += src[i];
}
return count;
} else {
// N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解
int mid = (from + to) / 2;
ForkJoinTask left = new ForkJoinTask(src, from, mid);
ForkJoinTask right = new ForkJoinTask(src, mid + 1, to);
invokeAll(left,right);
return left.join() + right.join();
}
}
}
/**
* ForkJoin实现
*/
public static void doForkJoin(int[] src) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask task = new ForkJoinTask(src, 0, 999);
pool.invoke(task);
System.out.println("The res is "+ task.join()
+" doForkJoin time:"+(System.currentTimeMillis()-start)+"ms");
}
/**
* 传统单线程实现
*/
public static void doNormal(int[] src) {
long start = System.currentTimeMillis();
int count = 0;
for(int i=0; i<1000;i++) {
try {
// 延时10ms 模拟业务处理
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count+= src[i];
}
System.out.println("The res is "+ count
+" doNormal time:"+(System.currentTimeMillis()-start)+"ms");
}
public static void main(String[] args) {
int src[] = new int[1000];
for(int i=0;i<1000;i++){
src[i] = i + 1;
}
doForkJoin(src);
doNormal(src);
}
}
ForkJoin的异步调用Demo
遍历指定目录(含子目录)找寻指定类型文件。
public class FileTask extends RecursiveAction{
private File path;//当前任务需要搜寻的目录
public FileTask(File path) {
this.path = path;
}
public static void main(String [] args){
try {
// 用一个 ForkJoinPool 实例调度总任务
ForkJoinPool pool = new ForkJoinPool();
FileTask task = new FileTask(new File("D:/"));
pool.execute(task);//异步调用
System.out.println("Start......");
Thread.sleep(1);
int count = 0;
for(int i = 0; i < 100; i++){
count = count + i;
}
System.out.println("Main Thread doing......, count = " + count);
task.join();//阻塞的方法
System.out.println("End......");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void compute() {
List<FileTask> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if(files != null) {
for(File file : files) {
if(file.isDirectory()) {
subTasks.add(new FileTask(file));
}else {
//检查
if(file.getAbsolutePath().endsWith("txt")) {
System.out.println("文件:"+file.getAbsolutePath());
}
}
}
if(!subTasks.isEmpty()) {
for(FileTask subTask:invokeAll(subTasks)) {
subTask.join();//等待子任务执行完成
}
}
}
}
}