简介原理
ForkJoin是由JDK1.7后提供多线并行处理框架。ForkJoin的框架的基本思想是分而治之。就是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果。
以下是我理解的流程原理图。
更加直观一点的图如下:
我们需要了解它的两个核心类:
- ForkJoinTask,就是执行具体处理逻辑的类,该类提供了在任务中执行fork和join的机制。但通常情况下我们不需要直接继承ForkJoinTask类,只需要继承它的2个子类:
RecursiveAction:用于没有返回结果的任务
RecursiveTask: 用于有返回结果的任务
2.ForkJoinPool,ForkJoinTask需要通过ForkJoinPool来执行。
通过ForkJoinPool forkJoinPool = ForkJoinPool.commonPool() 来获得,
也可通过构造,自定义设置属性来获取。一般使用前者就可以。
应用
由于是JDK提供的框架,所以无需引入其他依赖。
需求:导出业务数据库所有的用户数据。
分析:由于数据量特别大,所以查询是需要使用到多线程处理,否则一次性从mysql中捞出大量数据,会很耗性能,而且时间会很长。
首先创建分批任务类:
/**
* @Description 用户分批查询任务
* @Author gourd.hu
* @Date 2020/9/28 15:33
* @Version 1.0
*/
public class UserFindTask extends RecursiveTask<List<RbacUser>> {
/**
* 默认分批数量
*/
public static final int BATCH_COUNT = 10000;
/**
* 分批数量
*/
private Integer batchCount;
/**
* 任务最大的分批页
*/
private Integer maxBatchNo;
/**
* 任务最小的分批页
*/
private Integer minBatchNo;
/**
* 数据查询接口
*/
private RbacUserDao rbacUserDao;
public UserFindTask(Integer batchCount,Integer minBatchNo,Integer maxBatchNo,RbacUserDao rbacUserDao){
this.batchCount = batchCount;
this.minBatchNo = minBatchNo;
this.maxBatchNo = maxBatchNo;
this.rbacUserDao = rbacUserDao;
}
public UserFindTask(Integer minBatchNo,Integer maxBatchNo,RbacUserDao rbacUserDao){
this.batchCount = BATCH_COUNT;
this.minBatchNo = minBatchNo;
this.maxBatchNo = maxBatchNo;
this.rbacUserDao = rbacUserDao;
}
@Override
protected List<RbacUser> compute() {
// 相当于分页,当最小、最大分页数相同时,说明任务已切分完成,执行任务逻辑
if(maxBatchNo.equals(minBatchNo)){
Page page = new Page<>(minBatchNo,batchCount);
LambdaQueryWrapper<RbacUser> queryWrapper = new LambdaQueryWrapper();
queryWrapper.orderByDesc(RbacUser::getId);
return rbacUserDao.selectPage(page,queryWrapper).getRecords();
}
// 将任务分成2批次
int middleBatchNo = (maxBatchNo+minBatchNo) >>1 ;
// 每个批次创建各自子任务
UserFindTask userFindTask1 = new UserFindTask(batchCount,minBatchNo,middleBatchNo,rbacUserDao);
UserFindTask userFindTask2 = new UserFindTask(batchCount,middleBatchNo+1,maxBatchNo,rbacUserDao);
// 子任务执行(fork)
invokeAll(userFindTask1,userFindTask2);
// 任务汇总
List<RbacUser> userList1 = userFindTask1.join();
List<RbacUser> userList2 = userFindTask2.join();
// 自定义汇总逻辑
userList1.addAll(userList2);
return userList1;
}
}
主任务调用:
public List<RbacUser> findAll(){
// 数据量大,采用fork/join 多线程处理
Integer userCount = rbacUserDao.selectCount(null);
if(userCount == null || userCount ==0){
return null;
}
// 计算出最大分批数
int maxBatchNo = (int) Math.ceil((double)userCount / 1);
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
UserFindTask userFindTask = new UserFindTask(1,1,maxBatchNo,rbacUserDao);
// execute(ForkJoinTask) 异步执行tasks,无返回值
// forkJoinPool.execute(userFindTask);
// submit(ForkJoinTask) 异步执行,且带Task返回值,可通过task.get 实现同步到主线程
/*ForkJoinTask<List<RbacUser>> joinTask = forkJoinPool.submit(userFindTask);
try {
forkJoinPool.awaitTermination(60, TimeUnit.SECONDS);
List<RbacUser> rbacUsers = joinTask.get();
} catch (InterruptedException | ExecutionException e) {
log.error("批处理查询失败",e);
}*/
// invoke(ForkJoinTask) 有Join, tasks会被同步到主进程
List<RbacUser> rbacUsers = forkJoinPool.invoke(userFindTask);
// 关闭线程池
forkJoinPool.shutdown();
return rbacUsers;
}
到此,整合结束,其他业务场景也可根据实际情况应用,主要应用大数据量的插入或查询操作。当然这里也可以使用异步线程池实现此功能。
===============================================
代码均已上传至本人的开源项目
cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673