很多线上问题看起来是接口慢、CPU 高、任务堆积最后排查一圈才发现根因都绕不开线程池。线程池本身并不复杂真正容易出问题的是参数拍脑袋、队列无限长、拒绝策略没兜底、监控缺失以及业务方误以为“线程越多越快”。线程池治理的核心不是背几个参数而是理解任务从提交到执行完成的整条链路。一个任务进入线程池后大致会经历这几个阶段先判断当前工作线程数是否小于 corePoolSize如果小于就创建核心线程执行任务如果核心线程已满任务进入阻塞队列如果队列也满了才继续创建非核心线程直到 maximumPoolSize如果线程数也达到上限就触发拒绝策略。所以线程池调优不能只看 corePoolSize 和 maximumPoolSize还要把队列、任务耗时、提交速度、拒绝策略放在一起看。第一线程池参数要结合任务类型。如果任务主要是 CPU 密集型比如大量计算、加解密、压缩、规则匹配那么线程数不应该远大于 CPU 核数。线程太多只会增加上下文切换让 CPU 更忙但吞吐不一定提升。如果任务主要是 IO 密集型比如 RPC 调用、数据库访问、文件读写、网络请求那么线程数可以适当放大。因为线程大部分时间在等待外部资源增加线程数可以提高并发处理能力。但这里有一个常见误区IO 密集型也不是线程越多越好。线程数放大后下游服务、数据库连接池、Redis 连接池也要能承受否则只是把压力从应用内部转移到了下游。第二不要轻易使用无界队列。很多人创建线程池时喜欢用 LinkedBlockingQueue而且不设置容量。这样看起来“不会丢任务”但风险很大。提交速度一旦超过消费速度任务会持续堆积内存逐渐上涨最终可能导致 Full GC 频繁甚至 OOM。生产环境更推荐使用有界队列。队列容量本质上是系统能承受的缓冲区大小它应该由业务延迟要求、单任务耗时、峰值流量共同决定。比如一个任务平均耗时 100ms线程池有 20 个工作线程理论上每秒可以处理约 200 个任务。如果峰值提交量达到每秒 1000 个而队列容量设置成 10000看似能扛一会儿但后面的任务可能已经排队几十秒。这对用户请求类任务来说没有意义因为任务还没执行就已经超时了。第三拒绝策略必须有业务兜底。线程池触发拒绝不一定是坏事。它至少说明系统已经识别到自己扛不住了。真正危险的是拒绝后没有日志、没有指标、没有降级导致业务悄悄丢任务。常见拒绝策略有几种AbortPolicy 会直接抛异常适合必须感知失败的场景。CallerRunsPolicy 会让提交任务的线程自己执行任务可以形成一定反压但要小心把主流程拖慢。DiscardPolicy 会静默丢弃任务生产环境一般不建议直接使用。DiscardOldestPolicy 会丢弃队列中最旧的任务再尝试提交新任务适合少数允许覆盖旧任务的场景。实际项目中更推荐自定义拒绝策略记录关键日志打点监控指标必要时写入补偿队列或者返回明确的降级结果。第四线程池一定要可观测。没有监控的线程池线上排查基本靠猜。至少要关注这些指标当前线程数、活跃线程数、核心线程数、最大线程数、队列长度、队列剩余容量、任务完成总数、拒绝次数、任务执行耗时、任务排队耗时。其中“排队耗时”很容易被忽略但它非常关键。很多接口慢并不是业务执行慢而是任务在线程池里等了太久。只看任务执行耗时会误判问题。可以在任务提交时记录时间在真正执行前计算等待时间。这样就能区分“队列堆积导致慢”和“业务逻辑本身慢”。第五不同业务要隔离线程池。一个常见线上事故是多个业务共用同一个线程池。某个低优先级任务突然堆积把线程池占满结果核心接口也被拖垮。线程池隔离是非常实用的治理手段。比如用户请求、异步通知、报表任务、文件处理、消息消费最好根据重要程度和资源消耗拆分不同线程池。这样即使某一类任务异常也不会把整个应用的异步能力全部打满。第六线程池问题的排查思路。如果接口变慢先看线程池队列长度和活跃线程数。如果活跃线程数接近最大线程数队列持续增长说明消费能力不足或下游变慢。如果 CPU 很高要看线程数是否过大是否有大量线程争抢锁是否存在频繁上下文切换。如果任务大量失败要看拒绝次数、异常日志、下游超时和连接池耗尽情况。如果内存上涨要重点检查无界队列、任务对象大小以及任务是否携带大量上下文数据。线程池不是一个单独的性能开关而是业务流量、任务耗时、下游能力和系统资源之间的平衡器。真正稳的线程池配置不是某个固定公式算出来的而是在明确业务特征后通过压测、监控和线上反馈逐步校准出来的。总结一下线程池治理要看五件事。参数是否匹配任务类型队列是否有界拒绝策略是否可感知监控指标是否完整业务之间是否做好隔离。做到这些线程池才不只是“能跑”而是能在高峰和异常场景下稳住系统。