Java 线程池:参数语义、拒绝策略与线上稳定性排查
目标你不仅能背出ThreadPoolExecutor的 7 大参数还能在线上遇到「接口 RT 飙升 / CPU 异常 / 队列爆了 / 线程打满」时快速定位是任务特性问题还是线程池配置问题。1. 线程池到底在解决什么问题线程池要解决的核心矛盾只有两个资源复用线程创建/销毁有成本栈内存、内核调度、上下文切换。流量整形外部请求到达速率可能远大于系统处理能力需要通过队列/拒绝来保护系统。把线程池理解成一个「可控的并发阀门」你愿意同时并发处理多少任务线程数你愿意暂存多少任务队列容量超过上限怎么处理拒绝策略2.ThreadPoolExecutor七大参数不要背定义要背“决策链”构造参数corePoolSizemaximumPoolSizekeepAliveTimeTimeUnitworkQueuethreadFactoryhandler拒绝策略2.1 任务提交流程面试最常问的“执行顺序”当execute(task)线程数 core直接创建核心线程执行不进队列否则尝试入队workQueue.offer如果队列满了线程数 max创建非核心线程执行否则触发拒绝策略这条决策链决定了你选队列/配线程数的逻辑。2.2 三种典型队列选择决定线程增长策略LinkedBlockingQueue(有界)优点平滑吞吐稳定坑如果容量过大容易把延迟拖长队列排队时间巨大看起来“没拒绝但慢到不可用”ArrayBlockingQueue(有界)优点内存更紧凑性能可控坑容量同样要算SynchronousQueue(无容量)特点不存任务来一个必须立刻交给线程效果更倾向于涨线程数到max适用任务很短、希望尽快扩容并发但要能承受上下文切换实战经验大多数“业务线程池”应该用有界队列否则遇到突发流量要么 OOM无界队列要么线程爆炸SynchronousQueue max 配太大。3. 参数怎么估算用“服务能力”反推你需要先问清两个问题任务是 CPU 密集还是 IO 密集可接受的排队时间/最大延迟是多少3.1 CPU 密集型计算为主线程数建议Ncpu或Ncpu 1原因CPU 密集任务主要瓶颈在 CPU线程太多只会增加切换。3.2 IO 密集型等待为主RPC/DB/Redis/磁盘经典估算线程数 ≈ Ncpu * (1 等待时间/计算时间)你不必精确算但要有“等待占比越高线程数可以更大”的思维。3.3 队列容量不要凭感觉队列容量本质在控制系统允许积压多少任务积压会导致多大延迟一个可操作的估算方式峰值 QPSQ单任务平均耗时T线程池并发P稳定条件Q * T P粗略当Q*T P就一定会排队此时你要决定排队多久就算不可接受比如 200ms超过就拒绝返回友好错误/降级结论如果接口有 SLA队列应该偏小宁可拒绝也别“慢死”。4. 拒绝策略不是选一个名字是选“系统行为”JDK 内置 4 种AbortPolicy抛RejectedExecutionException适合内部任务、必须失败、上层能捕获并报警CallerRunsPolicy调用者线程自己跑效果给上游“反压”把压力传回去请求线程被拖慢坑可能把 Tomcat/Netty 线程拖死引发雪崩DiscardPolicy直接丢弃适合可丢任务埋点、统计DiscardOldestPolicy丢最老的队列任务适合最新数据更重要但要明确业务语义4.1 实战建议自定义拒绝策略业务上通常需要打点线程池名、队列长度、活动线程、拒绝次数快速失败并返回可理解的错误码必要时触发降级5. 常见坑面试线上高频5.1 用Executors.newFixedThreadPool/newSingleThreadExecutor坑点底层是LinkedBlockingQueue无界队列。表现不会拒绝任务无限堆积最终OOM 或 RT 极长排查看队列长度、堆内存、Full GC5.2 线程池“共享”导致互相拖垮RPC 回调、异步落库、消息消费、批处理混用一个线程池一个模块任务变慢会把全局线程池占满建议按任务类型/隔离域拆线程池核心链路与非核心链路隔离核心池更小更稳5.3 忽视线程命名与上下文传递不命名jstack 看不出是哪类任务上下文traceId、MDC、ThreadLocal不传递日志串不起来建议自定义ThreadFactory线程名带业务前缀用装饰器封装Runnable/Callable做 MDC 传递5.4 线程池关停不当未调用shutdown/awaitTermination导致应用无法优雅停机强行shutdownNow丢任务建议明确“可丢任务/不可丢任务”关停阶段输出未完成任务数6. 线上排查手册按“现象 - 指标 - 根因”6.1 现象RT 飙升但 CPU 不高可能根因IO 慢DB/RPC/磁盘队列排队时间变长看什么线程池队列长度、提交速率、任务耗时分布下游依赖的 P99/超时怎么验证采样任务开始/结束时间拆解“排队 vs 执行”6.2 现象CPU 飙升线程数多切换频繁可能根因线程池并发过大IO 任务误配成超大并发任务里有忙等/自旋看什么操作系统上下文切换jstack 线程状态RUNNABLE 很多6.3 现象频繁拒绝可能根因峰值流量超过服务能力任务变慢下游慢、锁竞争、GC处理策略短期限流/降级/快速失败中期拆池隔离、扩容、优化慢点7. 面试背诵稿30 秒线程池的核心是用core/max queue reject做并发控制和保护。execute流程是小于 core 直接建线程执行否则入队队列满了再扩容到 max再满就按拒绝策略处理。线上我会用有界队列避免无界堆积按任务类型拆池隔离线程要命名并做监控活跃线程、队列长度、拒绝次数、任务耗时。出现 RT 飙升时重点拆解“排队时间 vs 执行时间”再结合下游依赖慢点定位根因。