介绍
在项目中我们经常要处理一些大数据量的数据,譬如有100万的数据处理后进行入库,当然我们可以用springBatch框架,但是大部分情况下我们可能只需要开启多线程处理就行了,之前每次遇到新项目都是重写,或者把之前的代码改下很麻烦,而且网上给的工具类大部分不能返回成功条数,失败条数,失败的原始数据和失败的堆栈信息,返回结果。满足不了我的需求,因此自己写了一个工具类。
注意事项
因为我自己写的工具类返回的信息比较详细,如果数据量大而且报错信息又很多话,有可能OOM,自己只要改下HandleSupplier类errorData.setErrorException(ExceptionUtil.stacktraceToString(ex))
代码即可。
有啥问题欢迎评论,我会第一时间回复大家,也可以私信我。加我微信
使用方式
使用方式很简单只需要两步
第一:实现 ITask 接口
第二:调用 multiThreadUtil的execute() 方法
public interface ITask<E> {
/**
* 任务执行方法接口<BR>
* 方法名:execute<BR>
* @param e 传入对象
* @param params 其他辅助参数
* @return T<BR> 返回值类型
*/
ResultSingleBean execute(E e, Map<String, Object> params);
}
/**
* @param data 数据
* @param params 扩展参数
* @param task 任务实现类
* @param executor 线程池可以为空
* @param threadCount 线程个数 默认10
*/
public ResultBean execute(List<T> data, Map<String, Object> params, ITask<T> task, Executor executor, Integer threadCount)
例子
@Data
public class ListDataTest implements ITask<String> {
@Override
public ResultSingleBean execute(String str, Map params) {
ResultSingleBean resultSingleBean = new ResultSingleBean();
String ss = str.replace("小明", "");
Integer integer = Integer.valueOf(ss);
if (integer % 2 == 0) {
throw new RuntimeException("错误了");
}
UserBean userBean = new UserBean();
userBean.setName(str);
resultSingleBean.setData(userBean);
resultSingleBean.setSuccess(true);
return resultSingleBean;
}
public static void main(String[] args) {
ListDataTest listDataTest = new ListDataTest();
List<String> data = new ArrayList<String>(33);
for (int i = 0; i < 34; i++) {
data.add("小明" + i);
}
// 辅助参数 加数
Map<String, Object> params = new HashMap<>();
params.put("addNum", 4);
MultiThreadUtil multiThreadUtil = new MultiThreadUtil();
ResultBean<UserBean> execute = multiThreadUtil.execute(data, params, listDataTest, null, 3);
System.out.println("====================================");
System.out.println(JSON.toJSONString(execute));
}
}
返回结果
具体代码
保存失败信息实体类
/**
* @Package: com.example.thread.task
* @ClassName: ErrorData
* @Author: xu kang kai
* @Description:
* @Date: 2022/12/7 15:42
* @Version: 1.0
*/
@Data
public class ErrorData {
//原始数据
private String origData;
// 处理失败原因
private String errorException;
}
线程实现类这里实现的是Supplier 类似于Runabble。
package com.example.thread.task;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/**
* @Package: com.example.thread.task
* @ClassName: HandleSupplier
* @Author: xu kang kai
* @Description:
* @Date: 2022/12/7 15:35
* @Version: 1.0
*/
@Slf4j
public class HandleSupplier<E> implements Supplier<ResultBean> {
// 线程名称
private String threadName = "";
// 需要处理的数据
private List<E> data;
// 辅助参数
private Map<String, Object> params;
// 具体执行任务
private ITask<E> task;
public HandleSupplier(String threadName, List<E> data, Map<String, Object> params,
ITask<E> task) {
this.threadName = threadName;
this.data = data;
this.params = params;
this.task = task;
}
@Override
public ResultBean get() {
// 该线程中所有数据处理返回结果
ResultBean resultBean = new ResultBean();
if (!ObjectUtils.isEmpty(data)) {
log.info("线程:{},共处理:{}个数据,开始处理......", threadName, data.size());
Integer succes = 0;
Integer error = 0;
List<Object> list = new ArrayList<>();
List<ErrorData> errorDataList = new ArrayList<>();
// 循环处理每个数据
for (int i = 0; i < data.size(); i++) {
// 需要执行的数据
E e = data.get(i);
// 将数据执行结果加入到结果集中
ResultSingleBean<Object> execute = null;
try {
execute = task.execute(e, params);
} catch (Exception ex) {
log.error("执行任务失败", ex);
execute = new ResultSingleBean<>();
execute.setSuccess(false);
ErrorData errorData = new ErrorData();
errorData.setOrigData(JSON.toJSONString(e));
errorData.setErrorException(ExceptionUtil.stacktraceToString(ex));
execute.setErrorData(errorData);
}
if (execute.isSuccess) {
succes = succes + 1;
} else {
error = error + 1;
}
if (null != execute.getErrorData()) {
errorDataList.add(execute.getErrorData());
}
if (!ObjectUtils.isEmpty(execute.getData())) {
list.add(execute.getData());
}
}
resultBean.setErrorNum(error);
resultBean.setSuccessNum(succes);
resultBean.setErrorDataList(errorDataList);
resultBean.setLists(list);
}
return resultBean;
}
}
任务接口类
package com.example.thread.task;
import java.util.Map;
/**
* 任务处理接口
* 具体业务逻辑可实现该接口
* T 返回值类型
* E 传入值类型
*/
public interface ITask<E> {
/**
* 任务执行方法接口<BR>
* 方法名:execute<BR>
* @param e 传入对象
* @param params 其他辅助参数
* @return T<BR> 返回值类型
*/
ResultSingleBean execute(E e, Map<String, Object> params);
}
多线程工具实现类
package com.example.thread.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* @ClassName: MultiThreadUtils.java
* @Description: 多线程工具类
*/
public class MultiThreadUtil<T> {
private static Logger logger = LoggerFactory.getLogger(MultiThreadUtil.class);
/**
* @param data 数据
* @param params 扩展参数
* @param task 任务实现类
* @param executor 线程池可以为空
* @param threadCount 线程个数 默认10
*/
public ResultBean execute(List<T> data, Map<String, Object> params, ITask<T> task, Executor executor, Integer threadCount) {
ResultBean resultBean = new ResultBean<>();
resultBean.setLists(new ArrayList());
resultBean.setErrorDataList(new ArrayList<>());
resultBean.setErrorNum(0);
resultBean.setSuccessNum(0);
if (null == threadCount) {
threadCount = 10;
}
// 开始时间(ms)
long l = System.currentTimeMillis();
// 数据量大小
int length = data.size();
// 每个线程处理的数据个数
int taskCount = length / threadCount;
List<CompletableFuture<ResultBean>> list = new ArrayList<>();
// 划分每个线程调用的数据
for (int i = 0; i < threadCount; i++) {
// 每个线程任务数据list
List<T> subData = null;
if (i == (threadCount - 1)) {
subData = data.subList(i * taskCount, length);
} else {
subData = data.subList(i * taskCount, (i + 1) * taskCount);
}
// 将数据分配给各个线程
HandleSupplier handleSupplier = new HandleSupplier<T>(String.valueOf(i), subData, params, task);
CompletableFuture<ResultBean> objectCompletableFuture = null;
if (null == executor) {
objectCompletableFuture = CompletableFuture.supplyAsync(handleSupplier);
} else {
objectCompletableFuture = CompletableFuture.supplyAsync(handleSupplier,executor);
}
list.add(objectCompletableFuture);
}
//为了获取所有线程执行完的结果 用CountDownLatch 和下面的 CompletableFuture.allOf.都是可以的join()
CompletableFuture<Void> allCF = CompletableFuture.allOf(list.toArray(new CompletableFuture[0]));
//阻塞,直到所有任务结束。任务complete就会执行, handler里面不一定会执行..
allCF.join();
for (CompletableFuture<ResultBean> resultBeanCompletableFuture : list) {
try {
ResultBean reb = resultBeanCompletableFuture.get();
if (!ObjectUtils.isEmpty(reb.getSuccessNum())) {
resultBean.setSuccessNum(resultBean.getSuccessNum() + reb.getSuccessNum());
}
if (!ObjectUtils.isEmpty(reb.getErrorNum())) {
resultBean.setErrorNum(resultBean.getErrorNum() + reb.getErrorNum());
}
if (!ObjectUtils.isEmpty(reb.getLists())) {
boolean b = resultBean.getLists().addAll(reb.getLists());
resultBean.setLists(resultBean.getLists());
}
if (!ObjectUtils.isEmpty(reb.getErrorDataList())) {
boolean b = resultBean.getErrorDataList().addAll(reb.getErrorDataList());
resultBean.setErrorDataList(resultBean.getErrorDataList());
}
} catch (Exception e) {
logger.error("获取线程执行结果异常", e);
}
}
// 执行结束时间
long end_l = System.currentTimeMillis();
logger.info("总耗时:{}ms", (end_l - l));
return resultBean;
}
}
package com.example.thread.task;
import lombok.Data;
import java.util.List;
/**
* @Package: com.example.thread.task
* @ClassName: ResultBean
* @Author: xu kang kai
* @Description:
* @Date: 2022/12/7 15:52
* @Version: 1.0
*/
@Data
public class ResultBean<T> {
//成功条数
private Integer successNum;
//失败条数
private Integer errorNum;
// 结果集
private List<T> lists;
// 失败数据返回结果集
public List<ErrorData> errorDataList;
}
package com.example.thread.task;
import lombok.Data;
/**
* @Package: com.example.thread.task
* @ClassName: ResultSingleBean
* @Author: xu kang kai
* @Description: 处理每个任务返回结果
* @Date: 2022/12/7 15:40
* @Version: 1.0
*/
@Data
public class ResultSingleBean<T> {
// 是否成功
public boolean isSuccess = true;
// 需要返回数据
private T data;
// 失败数据返回结果集
public ErrorData errorData;
}
这个类不需要如果你要跑我上面的例子的话可以用下
import lombok.Data;
/**
* @Package: com.example.thread.task
* @ClassName: UserBean
* @Author: xu kang kai
* @Description:
* @Date: 2022/12/8 10:55
* @Version: 1.0
*/
@Data
public class UserBean {
private String name;
}