前言
今天来聊一聊forkJion并发处理框架,学习这个框架的实现思路对于编程思维会有很大的帮助
如果有小伙伴研究过像Storm、Spark这些大数据框架就很容易理解forkJion的编程思想
总体来说是将一个大任务拆分成很多个小任务来执行,而最终又将每个小任务执行的结果进行合并,最终完成这一大块任务
ForkJoinPool
ForkJoinPool是ExecutoeService接口的实现,它专为可以递归分解成小块的工作而设计
fork/join框架将任务分配给线程池中的工作线程,充分利用了多处理器的优势,提高程序性能
使用fork/join框架的第一步是编写执行一部分工作的代码,思路如下
// 如果当前工作部分达到需要拆分的大小
// 把当前工作拆分为两部分
// 调用这两部分任务并等待结果
// 否则直接做这项工作
拆分的大小(意思就是根据多少来拆小任务),用递归拆解的方式划分小任务
将此代码包装在ForkJionTask子类中,通常是RecursiveTask(可以返回结果)或RecursiveAction(无返回结果)
使用
RecursiveTask
/**
* @author 千手修罗
* @date 2021-01-01 13:45
* @description ForkJoInPool演示
*/
public class ForkJoInPoolDemo {
/**
* 创建一个ForkJoinPool
* parallelism:并行线程数量为3
* factory: 默认的线程池工厂
* handler: 线程执行异常处理器
* asyncMode: true (先进先出) false后进先出
*/
private static ForkJoinPool forkJoinPool = new ForkJoinPool(3, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
private static List<String> list = new ArrayList<>();
static {
for (int i = 1; i <= 100; i++) {
list.add(String.format("第%s个任务", i));
}
}
/**
* @author 千手修罗
* @date 2021-01-01 13:45
* @description ForkJoInPool演示
*/
static class Task extends RecursiveTask<String> {
/**
* 需要处理的任务
*/
private List list;
/**
* 本次任务处理的开始下标
*/
private int beginIndex;
/**
* 本次任务处理的结束下标
*/
private int endIndex;
/**
* 按多大划分一个小任务
*/
private int split;
public Task(List list, int split) {
this.list = list;
this.endIndex = 0;
this.endIndex = list.size();
this.split = split;
}
private Task(List list, int beginIndex, int endIndex, int split) {
this.list = list;
this.beginIndex = beginIndex;
this.endIndex = endIndex;
this.split = split;
}
@Override
public String compute() {
int count = endIndex - beginIndex;
if (count > split) {
int x = (beginIndex + endIndex) / 2;
Task beginJob = new Task(list, beginIndex, x, split);
beginJob.fork();
Task endJob = new Task(list, x, endIndex, split);
endJob.fork();
return beginJob.join() + "-" + endJob.join();
} else {
StringJoiner stringJoiner = new StringJoiner("-");
for (int i = beginIndex; i < endIndex; i++) {
stringJoiner.add(list.get(i).toString());
}
return stringJoiner.toString();
}
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
LocalDateTime now = LocalDateTime.now();
StringJoiner stringJoiner = new StringJoiner("-");
list.forEach(stringJoiner::add);
System.out.println(stringJoiner.toString());
System.out.println("forEach执行时间为:" + Duration.between(now, LocalDateTime.now()).toMillis() + "毫秒");
LocalDateTime now1 = LocalDateTime.now();
ForkJoinTask<String> submit = forkJoinPool.submit(new Task(list, 10));
String rs = submit.get();
System.out.println(rs);
System.out.println("ForkJoinTask执行时间为:" + Duration.between(now1, LocalDateTime.now()).toMillis() + "毫秒");
}
}
大小为100的list在迭代的情况下花费了18毫秒,而在ForkJoinPool的操作下花费了4毫秒
那肯定啊!多线程肯定比单线程执行效率更高啊
RecursiveAction
/**
* @author 千手修罗
* @date 2021-01-01 13:45
* @description ForkJoInPool演示
*/
public class ForkJoInPoolDemo {
/**
* 创建一个ForkJoinPool
* parallelism:并行线程数量为3
* factory: 默认的线程池工厂
* handler: 线程执行异常处理器
* asyncMode: true (先进先出) false后进先出
*/
private static ForkJoinPool forkJoinPool = new ForkJoinPool(3, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
private static List<String> list = new ArrayList<>();
static {
for (int i = 1; i <= 100; i++) {
list.add(String.format("第%s个任务", i));
}
}
@FunctionalInterface
interface TaskCallBack<T> {
/**
* 任务回调
*
* @param t
*/
void callback(T t);
}
/**
* @author 千手修罗
* @date 2021-01-01 13:45
* @description ForkJoInPool演示
*/
static class Task<T> extends RecursiveAction {
/**
* 需要处理的任务
*/
private List<T> list;
/**
* 本次任务处理的开始下标
*/
private int beginIndex;
/**
* 本次任务处理的结束下标
*/
private int endIndex;
/**
* 按多大划分一个小任务
*/
private int split;
/**
* 下标任务回调
*/
private TaskCallBack<T> taskCallBack;
public Task(List<T> list, int split, TaskCallBack<T> taskCallBack) {
this.list = list;
this.endIndex = 0;
this.endIndex = list.size();
this.split = split;
this.taskCallBack = taskCallBack;
}
private Task(List<T> list, int beginIndex, int endIndex, int split, TaskCallBack<T> taskCallBack) {
this.list = list;
this.beginIndex = beginIndex;
this.endIndex = endIndex;
this.split = split;
this.taskCallBack = taskCallBack;
}
@Override
public void compute() {
int count = endIndex - beginIndex;
if (count > split) {
int x = (beginIndex + endIndex) / 2;
Task beginJob = new Task(list, beginIndex, x, split, taskCallBack);
beginJob.fork();
Task endJob = new Task(list, x, endIndex, split, taskCallBack);
endJob.fork();
beginJob.join();
endJob.join();
} else {
for (int i = beginIndex; i < endIndex; i++) {
taskCallBack.callback(list.get(i));
}
}
}
}
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
StringJoiner stringJoiner = new StringJoiner("-");
list.forEach(stringJoiner::add);
System.out.println(stringJoiner.toString());
System.out.println("forEach执行时间为:" + Duration.between(now, LocalDateTime.now()).toMillis() + "毫秒");
LocalDateTime now1 = LocalDateTime.now();
StringJoiner stringJoiner1 = new StringJoiner("-");
forkJoinPool.invoke(new Task(list, 10, (s) -> stringJoiner1.add((String) s)));
System.out.println(stringJoiner1.toString());
System.out.println("ForkJoinTask执行时间为:" + Duration.between(now1, LocalDateTime.now()).toMillis() + "毫秒");
}
}
虽然RecursiveAction无返回值,但我们可以扩展出一个方法将每个元素传达到调用者手上,至于他想干嘛就干嘛
总结
对于forkJion的原理想必不用多说大家也能够想得到,无非是在线程池基础上对递归的封装
compute方法让我们自己来决定什么时候需要拆解任务什么时候需要执行任务
-
调用fork方法说白了就是再调用compute方法
-
调用join方法说白了就是在等待这个任务执行完毕
对于这个框架的应用场景其实还是蛮多的:文件遍历、循环列表组装数据、统计某个数值.....
只要能够通过递归来实现的应用场景都可以使用fork/join框架来提高性能