
线程池是并发里最“工程化”的一块写错了不是慢一点而是直接把服务拖死。你要能讲清楚这三件事线程池的参数和执行流程为什么会 OOM/耗尽/延迟爆炸线上如何排查与调优你也可以把线程池问题总结成一句话线程池是“背压装置”不是“无限吞吐装置”。1. 线程池到底解决什么问题控制并发量避免“无限创建线程”复用线程降低创建/销毁成本统一任务调度入口便于监控与治理2. ThreadPoolExecutor 的关键参数必须会corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime非核心线程空闲多久回收workQueue任务队列threadFactory线程命名/优先级/守护线程RejectedExecutionHandler拒绝策略一个重要的工程点线程池的“容量”不仅是 maxPoolSize还取决于队列类型与大小。工程上建议你额外关注队列是否有界任务平均耗时与 P99拒绝次数最关键的告警指标之一3. 执行流程任务来了到底怎么走按顺序记当提交一个任务时典型流程是如果运行线程数 corePoolSize创建核心线程执行否则尝试入队workQueue如果队列满若运行线程数 maximumPoolSize创建非核心线程执行否则触发拒绝策略这段流程是面试回答的主骨架。3.1 一个可复用的线程池模板线程命名 有界队列 拒绝计数importjava.util.concurrent.*;importjava.util.concurrent.atomic.AtomicLong;publicclassPools{publicstaticThreadPoolExecutornewBizPool(Stringname,intcore,intmax,intqueueSize){AtomicLongseqnewAtomicLong();AtomicLongrejectnewAtomicLong();ThreadFactorytfr-{ThreadtnewThread(r);t.setName(name-seq.incrementAndGet());t.setDaemon(false);returnt;};RejectedExecutionHandlerreh(r,e)-{reject.incrementAndGet();thrownewRejectedExecutionException(rejected: name);};returnnewThreadPoolExecutor(core,max,60,TimeUnit.SECONDS,newArrayBlockingQueue(queueSize),tf,reh);}}这个模板背后的工程原则线程必须命名否则线上排查基本不可读队列必须有界避免无界堆积拒绝必须可观测至少能统计次数并告警4. 队列的选择决定了线程池的行为模型4.1 LinkedBlockingQueue无界/可大风险如果队列无界任务会无限堆积最终OOM或延迟爆炸现象线程数稳定在 corePoolSize队列不断增长为什么“无界队列很危险”线程数通常稳定在 core你以为系统“还能接单”其实是在队列里排队最终把延迟拖到不可用4.2 ArrayBlockingQueue有界优点队列可控能触发扩容到 maxPoolSize再触发拒绝适合需要对系统施加背压4.3 SynchronousQueue不存任务直接交接特点不存储任务提交必须立刻被线程接住容易快速扩线程到 max适合短任务、高吞吐4.4 PriorityBlockingQueue优先级风险任务排序与饥饿问题任务堆积同样会导致内存压力5. 拒绝策略你的系统如何“优雅失败”常见策略AbortPolicy默认直接抛异常CallerRunsPolicy让提交任务的线程执行最常用于背压DiscardPolicy直接丢弃DiscardOldestPolicy丢弃队列最老任务工程建议业务接口通常更适合CallerRunsPolicy或自定义拒绝记录日志监控降级避免无脑丢任务6. 常见事故为什么线程池会把服务拖死6.1 无界队列导致 OOM任务生产速度 消费速度队列无限增长6.2 线程数太大导致上下文切换爆炸CPU 被切换吃满RT 抖动严重6.3 锁/IO 导致线程长期占用线程池本质是资源池临界区过大或 IO 阻塞会导致“池被占满”7. 线上排查快速判断是线程池问题还是下游慢7.1 先看线程池指标你至少要能拿到这些poolSize/activeCountqueue.size()completedTaskCount拒绝次数你可以把线程池状态分成 3 种“危险画像”队列堆积型queue 持续增长active 不高任务消费跟不上线程耗尽型active 接近 maxqueue 也大下游慢/任务太慢拒绝风暴型reject 突增容量不足或突发流量7.2 抓线程栈大量线程停在同一个 RPC/DB 调用下游慢大量线程停在锁竞争/等待应用内部瓶颈8. 调优思路先定目标再改参数不要上来就背公式按这个逻辑说任务是 CPU 密集还是 IO 密集期望的是吞吐还是延迟是否允许排队排队多久算超时常用经验CPU 密集线程数接近 CPU 核心数IO 密集线程数可以更大但要以“下游承载能力”为上限典型配置建议给面试/落地都好用Web 请求主线程不要把慢 IO 放进去异步任务单独业务线程池别复用公共池IO 密集任务有界队列 适度增大 max但必须配合超时与熔断9. 面试追问 QA高频Q为什么要有界队列A无界队列会把压力转移成“无限排队”最终延迟爆炸或 OOM无法形成背压。QCallerRunsPolicy 有什么作用A把压力反向传给调用方线程天然形成背压但要注意别把 Web 线程拖死。Q怎么判断是下游慢还是线程池配置问题A看 active/queue/reject再抓线程栈判断线程都卡在哪里。10. 面试表达30 秒讲清楚线程池通过复用线程与队列控制并发避免无限建线程。提交任务流程小于 core 先建线程否则入队队列满再扩到 max再满触发拒绝。队列决定行为无界队列风险是堆积导致 OOM/延迟爆炸有界队列能形成背压。拒绝策略是系统的“失败策略”常用 CallerRuns 或自定义降级。线上排查看 active/queue/reject/线程栈先判断是下游慢还是内部锁/IO 导致池耗尽。11. 总结线程池调优的核心是“容量可控 背压清晰 指标可观测”无界队列是事故高发点线上先看指标再看线程栈别盲调参数