多线程实战总结

如下:有问题的代码

// 创建一个固定大小为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;
}

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值