- 核心业务,不容丢失:如果任务非常重要,不能丢失,可以考虑:
- CallerRunsPolicy:调用线程承担任务执行压力,是否可支撑;
- 自定义策略:尝试持久化到 MQ 或 DB,然后由专门的消费组补偿任务处理;
- AbortPolicy:如果希望系统快速失败并由上层进行重试或熔断。
- 非核心业务,可容忍部分丢失:
- DiscardOldestPolicy:新任务更重要时,如行情推送;
- DiscardPolicy:边缘业务场景,比如一些 pv 统计等,丢失了无所谓;
- 及时的进行监控查看,了解任务的丢失情况。
3. 结合线程池参数综合考虑:
- 拒绝策略的选择也与线程池的队列类型(有界 / 无界)、队列容量、maximumPoolSize 等参数密切相关。
- 如果使用无界队列 LinkedBlockingQueue 的无参构造,只有机器内存不够时才会进行拒绝策略,不过这种极端场景已经不是影响线程池本身,内存不够可能导致 Java 进程被操作系统直接 kill 可能。
- 如果使用有界队列,需要权衡队列的大小,核心场景甚至可以动态追踪阻塞队列大小,以及动态调整队列大小来保证核心业务的正常流转。
- 充分测试和监控:无论选择哪种策略,都必须在压测环境中充分测试其行为,并在线上环境建立完善的监控体系,监控线程池的各项指标(活跃线程数、队列长度、任务完成数、任务拒绝数等)。当拒绝发生时,应有相应的告警通知。
拒绝策略小结:
策略的选择跟我们大多数的系统设计哲学是保持一致的,都是在应对不同的场景中,做出一定的 trade off。最好的策略需要根据业务场景、系统容忍度、资源等方面的综合考量,一个黄金的实践原则:拒绝事件做好监控告警、根据业务 SLA 定义策略,是否可丢失,快速失败等,定期的进行压力测试,验证策略的有效性。
5.5 池隔离实践
核心思想:根据任务的资源类型 、优先级和业务特性 ,划分多个独立的线程池,避免不同性质的任务相互干扰。
1. 隔离维度:
- 资源类型:CPU 密集型 vs I/O 密集型任务
- 执行时长:短时任务(毫秒级) vs 长时任务(分钟级)
- 实时性要求:高实时性 vs 可延迟(最终一致即可)
- 业务重要性:支付交易(高优先级) vs 日志清理(低优先级)
- 是否依赖外部资源:例如,访问特定数据库、调用特定第三方 API 的任务可以归为一类。
2. 不同业务场景线程池独立使用:在不同的业务场景下,为自己的特定业务,创建独立的线程池。
- 线程命名:通过 ThreadFactory 为每个线程池及其线程设置有意义的名称,例如 netty-io-compress-pool-% d,excel-export-pool-% d, 主要方便区别不同的业务场景以及问题排查。
- 参数调优:不同的业务场景设置不同的参数。
- corePoolSize, maximumPoolSize:CPU 密集型的计算任务可以设置小点减少上下文的切换,I/O 密集型可以较大,在 io 阻塞等待期间,多去处理其他任务。
- 阻塞队列 blockQueue:选择合适的队列类型,以及设置合理的队列大小。
- RejectedExecutionHandler:有内置的四种的策略以及自定义策略选择,一般建议做好日志、监控以及兜底的处理。
3. 自定义 Executor 避免线程池共用
// 创建CPU密集型任务线程池(线程数=CPU核心数)
ExecutorService cpuIntensiveExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(), // 核心线程数=CPU核心数
Runtime.getRuntime().availableProcessors(), // 最大线程数=CPU核心数
30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(500),
new ThreadFactoryBuilder()
.setNameFormat("cpu-pool-%d")
.setPriority(Thread.MAX_PRIORITY) // 提高优先级
.build(),
new ThreadPoolExecutor.AbortPolicy() // 直接拒绝
);
// 使用示例
CompletableFuture.supplyAsync(() -> {
// 矩阵计算等CPU密集型任务
double[][] result = matrixMultiply(largeMatrixA, largeMatrixB);
return result;
}, cpuIntensiveExecutor)
.thenAccept(result -> {
System.out.println("计算结果维度: " + result.length + "x" + result[0].length);
});
线程池隔离小结:
专池专用的本质是通过物理隔离实现:
- 资源保障 :关键业务独占线程资源
- 故障隔离 :避免级联雪崩
- 性能优化 :针对任务类型最大化吞吐量
最终呈现的效果是像专业厨房的分区(切配区 / 炒菜区 / 面点区)一样,让每个线程池专注处理同类任务,提升整体效率和可靠性。
六、总结
线程池是 Java 并发编程的核心组件,通过复用线程减少资源开销,提升系统吞吐量。其核心设计包括线程复用机制 、任务队列和拒绝策略 ,通过 ThreadPoolExecutor 的参数(核心线程数、最大线程数、队列容量等)实现灵活的资源控制。线程池的生命周期由 RUNNING、SHUTDOWN 等状态管理,确保任务有序执行或终止。
内置线程池(如 Executors.newCachedThreadPool)虽便捷,但存在内存溢出或无界队列堆积的风险,需谨慎选择。invokeAll 的超时失效和 submit 提交任务的异常消失是常见陷阱需通过正确处理中断和检查 Future.get () 规避。
最佳实践包括:
- 异常处理:通过 afterExecute 来对发生的异常进行兜底处理,任务细粒度的 try catch 或 UncaughtExceptionH 捕获异常处理防止线程崩溃退出;
- 拒绝策略:根据业务选择拒绝策略或自定义降级逻辑,生产级应用建议尽量自定义处理;
- 线程隔离 :按任务类型(CPU/I/O)或优先级划分线程池,避免资源竞争。
合理使用线程池能显著提升性能,但需结合业务场景精细调参,确保稳定性和可维护性,希望这篇文章能给大家带来一些生产实践上的指导,减少一些因为不熟悉线程池相关原理生产误用导致的一些问题。

被折叠的 条评论
为什么被折叠?



