一次多线程改造实践:基于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
- 优点:按任务完成顺序获取结果,而非提交顺序
流程:
submit(Callable)
:提交任务到线程池take()
/poll()
:获取已完成任务的Future
get()
:获取具体结果或异常
这种模式避免了遍历所有 Future
检查状态,提高聚合效率
🎡 使用建议与扩展
✅ 使用建议
- 并发任务需满足线程安全、无共享状态
- 线程池应设置合理核心线程数、队列大小与拒绝策略
- 对于大量任务建议配合限流器如
Semaphore
📄 场景拓展
- 批量导入 / 导出数据
- 大文件分段处理
- 对多个接口异步调用并合并结果(如聚合查询)
📝 总结
通过使用 ExecutorService + CompletionService
对串行任务进行并发改造,能够在保证业务正确性的前提下大幅提升处理效率 ,尤其适合独立、重复、大量的任务场景。
本次实践中,改造后的实现满足了:
- 前端同步获取结果的业务需求
- 可控的并发处理与资源使用
- 良好的异常处理与日志记录
是一种在生产环境下非常推荐的并发模式。