一次多线程改造实践:基于ExecutorService + CompletionService的并发处理优化

一次多线程改造实践:基于ExecutorService + CompletionService的并发处理优化

📄 背景与业务场景

在企业业务系统中,我们经常需要对一批独立任务进行重复性的处理,例如:

  • 为多个合同生成应收账单
  • 对多个客户批量发送通知
  • 针对多租户并行初始化资源

这类任务具备天然的并行性,即任务之间互不影响、处理逻辑相同,完全可以使用多线程优化执行效率。

本文以"为多个合同生成应收单"为例,记录一次从串行到并发的多线程改造实践 ,并重点介绍 ExecutorService + CompletionService 的使用方式。

📆 需求分析

原始功能需求:

  • 输入为若干合同ID(可为全部合同或指定ID集合)
  • 对每一个合同执行账单生成逻辑
  • 聚合返回所有生成成功的应收单ID列表
  • 该接口为前端"提交并审批"流程的一部分,必须同步返回处理结果

技术限制与期望:

  • 合同数量多时串行处理耗时较长,需提升处理效率
  • 合同任务之间互不依赖,可并行
  • 需要对失败任务记录日志
  • 并发线程数应可控,防止系统资源占满

📈 原始串行代码(伪代码)

ini 复制代码
List<Long> resultIds = new ArrayList<>();
for (Long contractId : contractIds) {
    Contract contract = contractService.getContract(contractId);
    if (contract == null) throw new RuntimeException("Contract not found");
​
    Map<Long, Bill> bills = billService.generate(contract);
    for (Bill bill : bills.values()) {
        Long billId = billRepository.save(bill);
        resultIds.add(billId);
    }
}
return resultIds;

问题:

  • 完全串行,处理效率低
  • 合同处理耗时不均,无法优化瓶颈

🛠️ 改造方案:使用线程池并发处理合同任务

1. 技术选型

  • ExecutorService:线程池统一管理线程资源,避免频繁创建销毁线程
  • CompletionService:结合线程池管理任务提交 + 异步结果获取,支持按完成顺序返回结果,便于聚合处理

2. 改造目标

  • 将每一个合同处理逻辑封装为 Callable<List<Long>>
  • 提交至线程池统一调度执行
  • 主线程收集 Future 返回结果并聚合
  • 控制最大并发线程数,保证系统安全

📃 改造后关键代码

ini 复制代码
int threadCount = Math.min(10, contractIds.size());
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CompletionService<List<Long>> completionService = new ExecutorCompletionService<>(executor);
​
// 提交任务
for (Long contractId : contractIds) {
    completionService.submit(() -> {
        Contract contract = contractService.getContract(contractId);
        if (contract == null) throw new RuntimeException("Contract missing");
​
        Map<Long, Bill> billMap = billService.generate(contract);
        List<Long> billIds = new ArrayList<>();
        for (Bill bill : billMap.values()) {
            Long billId = billRepository.save(bill);
            billIds.add(billId);
        }
        return billIds;
    });
}
​
// 收集结果
List<Long> allBillIds = new ArrayList<>();
for (int i = 0; i < contractIds.size(); i++) {
    try {
        Future<List<Long>> future = completionService.take(); // 阻塞直到有结果
        allBillIds.addAll(future.get());
    } catch (Exception e) {
        log.error("Task failed", e);
        // 可选:记录失败ID、重试等
    }
}
​
executor.shutdown();

📖 ExecutorService + CompletionService 原理解析

ExecutorService

  • Java 提供的线程池框架接口,常用实现如:

    • Executors.newFixedThreadPool(n) 固定线程数
    • ThreadPoolExecutor 自定义线程池参数
  • 管理线程的生命周期与复用,提高性能

CompletionService

  • 接口:java.util.concurrent.CompletionService
  • 实现类:ExecutorCompletionService
  • 优点:按任务完成顺序获取结果,而非提交顺序

流程:

  1. submit(Callable):提交任务到线程池
  2. take() / poll():获取已完成任务的 Future
  3. get():获取具体结果或异常

这种模式避免了遍历所有 Future 检查状态,提高聚合效率

🎡 使用建议与扩展

✅ 使用建议

  • 并发任务需满足线程安全、无共享状态
  • 线程池应设置合理核心线程数、队列大小与拒绝策略
  • 对于大量任务建议配合限流器如 Semaphore

📄 场景拓展

  • 批量导入 / 导出数据
  • 大文件分段处理
  • 对多个接口异步调用并合并结果(如聚合查询)

📝 总结

通过使用 ExecutorService + CompletionService 对串行任务进行并发改造,能够在保证业务正确性的前提下大幅提升处理效率 ,尤其适合独立、重复、大量的任务场景。

本次实践中,改造后的实现满足了:

  • 前端同步获取结果的业务需求
  • 可控的并发处理与资源使用
  • 良好的异常处理与日志记录

是一种在生产环境下非常推荐的并发模式。

相关推荐
程序员岳焱4 小时前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化
麦兜*5 小时前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
大只鹅5 小时前
解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
spring boot·后端·elasticsearch
ai小鬼头5 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
IT_10246 小时前
Spring Boot项目开发实战销售管理系统——数据库设计!
java·开发语言·数据库·spring boot·后端·oracle
bobz9656 小时前
动态规划
后端
stark张宇6 小时前
VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
linux·后端
亚力山大抵7 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍7 小时前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端
CHENWENFEIc8 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试