如下:有问题的代码
// 创建一个固定大小为20的线程池,当线程空闲时间达到100毫秒时,这些线程会被回收。
// 工作队列的容量设置为2000,超出这个容量时,新提交的任务会被拒绝。
private final ExecutorService executorService = new ThreadPoolExecutor(20, 20, 100L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(2000));
// delete 方法,用于执行删除操作。
private void delete(MkuBatchDeleteHandleDTO task) {
//前置校验...
// 使用线程安全的方式创建一个列表,用于存放结果。
List<BatchDeleteMkuExcelDTO> synchronizedResultList = Collections.synchronizedList(new ArrayList<>());
// 将要删除的项目总数转换为BigDecimal类型,可能是为了进行后续的计算。
BigDecimal total = new BigDecimal(task.getDeleteList().size()); // 假设为10000
// 使用CountDownLatch来同步等待所有的删除任务完成。
CountDownLatch latch = new CountDownLatch(task.getDeleteList().size());
// 遍历所有要删除的项目。
for (BatchDeleteMkuExcelDTO item : task.getDeleteList()) {
// 提交一个新的删除任务到线程池。
executorService.submit(() -> {
try {
//格式校验
//业务校验
//删除mku
delete(item);
// 删除成功后,将结果添加到结果列表。
synchronizedResultList.add(new BatchDeleteMkuExcelDTO(item.getMkuId(), "删除成功"));
} catch (Exception e) {
// 异常处理略去...
} finally {
// 无论成功还是异常,都将countDownLatch的计数减一。
latch.countDown();
}
});
}
// 关闭线程池,不再接受新的任务。
executorService.shutdown();
try {
// 等待所有删除任务执行完毕。
latch.await();
} catch (InterruptedException e) {
// 如果当前线程在等待时被中断,则重新设置中断状态,并记录日志。
Thread.currentThread().interrupt();
log.error("MkuBatchDeleteJob was interrupted", e);
}
// 将处理结果集设置回task对象中。
task.setDeleteList(new ArrayList<>(synchronizedResultList));
}
存在问题一:
for循环中直接把10000个删除任务扔进了等待队列只有2000的线程池,导致等待队列一瞬间塞满,抛弃了后面的7980个任务(20个执行的和2000个等待的)
存在问题二:
CountDownLatch设置为10000,latch.countDown();会减,直到2月1日我才想明白为啥最后一直得不到结果,原因是
executorService.submit(() -> {try catch}这个问题,为啥呢,在for循环提交到线程池任务中,try catch 捕获的是当前线程的异常,但是,10000个任务扔进线程池出现了RejectedExecutionException,这个异常没有被捕获,相对的每个submit里的对应的线程,处理的没有异常,所以countDownLatch初始是10000,它没有被减到0,也不会减到0,因为后面7980的任务被扔了,所以下面就会一直等待它减为0,永远减不到,任务状态永远是执行中
最后的解决代码:
public void run(TaskRecordDO taskInfo) {
try {
// 尝试接受任务,可能会返回null或者一个MkuBatchDeleteHandleDTO对象
MkuBatchDeleteHandleDTO task = acceptTask(taskInfo);
// 如果任务为空,则抛出一个运行时异常
if (task == null){
throw new RuntimeException("task is null");
}
// 创建一个用于存放结果的列表
List<BatchDeleteMkuExcelDTO> result = new ArrayList<>();
// 解析任务,返回BatchDeleteMkuExcelDTO列表
List<BatchDeleteMkuExcelDTO> paramList = parse(task);
// 将列表分割成多个子列表,每个子列表最多包含BATCH_DELETE_PAGE_CNT个元素
List<List<BatchDeleteMkuExcelDTO>> partition = Lists.partition(paramList, BATCH_DELETE_PAGE_CNT);
// 如果分割后的列表不为空
if (CollectionUtils.isNotEmpty(partition)){
// 遍历每一个子列表
for (List<BatchDeleteMkuExcelDTO> batchDeleteMkuExcelDTOS : partition) {
// 对每一个子列表执行删除操作,并返回结果
List<BatchDeleteMkuExcelDTO> partitionResultList = deleteBatch(batchDeleteMkuExcelDTOS, task.getOperator());
// 将结果添加到总结果列表中
result.addAll(partitionResultList);
}
}
// 设置任务中的删除列表为操作结果
task.setDeleteList(result);
// 导出操作结果
export(task);
// 记录日志,任务执行完毕,包括任务ID和耗时
log.info("MkuAppSceneMarkJob.doBatchDeleteMku 任务{}执行完毕,耗时:{}", taskInfo.getTaskId(), (task != null ? task.getLastTime() : null));
} catch (Exception e) {
// 如果在任务执行过程中遇到任何异常,记录错误日志
log.error("MkuBatchDeleteJob.doBatchDeleteMku error, target:{} ,errMsg:{}", JSON.toJSONString(taskInfo), e);
// 执行错误处理方法,传入任务信息和异常信息
errHandle(taskInfo, e.toString());
}
}
- 分批次处理,保证进入线程池的任务数不能超过最大等待队列(分批每批处理2000任务)
- 每批次单独处理,拿到多线程下最终的保证正确的数据
每批次处理:
private List<BatchDeleteMkuExcelDTO> deleteBatch(List<BatchDeleteMkuExcelDTO> list, String operator) {
// 创建一个用于存储结果的列表
List<BatchDeleteMkuExcelDTO> resultList = Lists.newArrayList();
// 如果输入列表为空,则直接返回空的结果列表
if (CollectionUtils.isEmpty(list)) {
return resultList;
}
// 创建一个HashMap用于存储Future对象,Future代表异步计算的结果
HashMap<String, Future> futureMap = Maps.newHashMap();
// errMsg初始化为空字符串,但在这个方法中未使用
final String errMsg = Constant.EMPTY_STR;
// 遍历输入列表中的每个BatchDeleteMkuExcelDTO对象
for (BatchDeleteMkuExcelDTO item : list) {
// 对每个对象创建一个异步任务,并将其提交到线程池
Future<BatchDeleteMkuExcelDTO> future = executorService.submit(() -> {
// 创建一个新的BatchDeleteMkuExcelDTO对象用于存储结果
BatchDeleteMkuExcelDTO batchDeleteMkuExcelDTO = new BatchDeleteMkuExcelDTO();
batchDeleteMkuExcelDTO.setMkuId(item.getMkuId());
try {
// 执行格式校验
formValidate(item);
// 执行业务校验
bizValidate(item);
// 执行删除操作
delete(item);
// 设置删除结果为成功
batchDeleteMkuExcelDTO.setDeleteResult("删除成功");
return batchDeleteMkuExcelDTO;
} catch (Exception e) {
// 如果在删除过程中发生异常,记录错误日志
String error = e.getMessage();
log.error("MkuBatchDeleteJob error, target:{} ,errMsg:{}", JSON.toJSONString(item), e);
// 发送业务告警
Profiler.businessAlarm(UmpKeyConstant.BUSINESS_KEY_TASK_WARNING, ("excel批量导入-批量删除Mku异常:" + e + JSON.toJSONString(item)));
// 设置删除结果为失败
batchDeleteMkuExcelDTO.setDeleteResult("删除失败");
return batchDeleteMkuExcelDTO;
} finally {
// 在finally块中记录日志,无论成功或异常都会执行
log.info("#mkt-log#|#jdi-spsc-product#|#{}#|#{}#|#{}#|##|##|##|#{}#|#{}#|##|#log-end#", operator,
LocalDateTime.now(),
"MKU批量删除",
errMsg,
JSON.toJSONString(item));
}
});
// 将Future对象和对应的MKU ID存入HashMap中
futureMap.put(item.getMkuId(), future);
}
// 获取HashMap中所有的键(MKU ID)
Set<String> keySet = futureMap.keySet();
for (String key : keySet) {
try {
// 从HashMap中获取对应的Future对象
Future<BatchDeleteMkuExcelDTO> future = futureMap.get(key);
// 等待异步任务完成,并在30秒内获取结果
BatchDeleteMkuExcelDTO batchDeleteMkuExcelDTO = future.get(30_000, TimeUnit.MILLISECONDS);
// 将结果添加到结果列表中
resultList.add(batchDeleteMkuExcelDTO);
// 记录日志:处理结果已添加到结果列表
log.info("deleteBatch handle result future add finish param={}", key);
} catch (Exception e) {
// 如果获取结果时发生异常(如超时),记录错误日志
log.error("deleteBatch batchDeleteMkuExcelDTO get result time out", e);
// 将一个表示删除失败的BatchDeleteMkuExcelDTO对象添加到结果列表中
resultList.add(new BatchDeleteMkuExcelDTO(key, "删除失败"));
}
}
// 返回包含所有删除结果的列表
return resultList;
}