[面试题] 线程池核心参数怎么设?IO密集型和CPU密集型有什么区别?
版块 A:知识讲解 (Knowledge Recap)
ThreadPoolExecutor 七大核心参数:
| 参数 | 含义 | 常见配置建议 |
|---|---|---|
corePoolSize | 核心线程数(常驻) | CPU型:CPU核数;IO型:CPU核数 × 2 或更多 |
maximumPoolSize | 最大线程数 | 根据业务峰值压测决定 |
keepAliveTime | 非核心线程空闲存活时间 | 一般 60s |
workQueue | 任务阻塞队列 | 避免使用无界队列(OOM风险) |
threadFactory | 线程工厂 | 务必命名,方便排查 |
handler | 拒绝策略 | 默认 AbortPolicy,生产建议自定义 |
unit | keepAliveTime 的时间单位 | — |
IO 型 vs CPU 型的本质区别:
| 类型 | 线程状态 | 核心策略 | 典型场景 |
|---|---|---|---|
| CPU 密集型 | 常处于运行态,吃 CPU | 线程数 ≈ CPU 核数,减少上下文切换 | 加解密、图像处理、计算 |
| IO 密集型 | 常处于阻塞态,等 IO | 线程数可以远大于 CPU 核数,提升并发 | 数据库查询、HTTP 调用、文件读写 |
线程池健康三级警戒:
- 核心线程全满 → 正常范围,任务入队
- 队列也满了 → 开始创建非核心线程(⚠️ 警惕!)
- 非核心线程也满 → 触发拒绝策略(🚨 故障!)
版块 B:面试实战 (Combat Script)
Q:线程池的核心参数你们生产环境是怎么配的?IO 密集型和 CPU 密集型线程池有什么区别?
破局思路:面试官在考你是否理解”线程数背后的资源模型”。高手的回答一定会从 IO 型和 CPU 型的本质区别出发,再延伸到线程池监控和动态调整,这才是有深度的工程师的回答方式。
💬 高手逐字稿
关于线程池的核心参数,我先从 IO 密集型和 CPU 密集型的本质区别说起,这是配置参数的理论基础。
CPU 密集型任务,线程大部分时间都在消耗 CPU 做计算,比如加解密、数据压缩。这种场景下,线程数超过 CPU 核数反而有害,因为多出来的线程只会频繁发生上下文切换,白白消耗 CPU。所以 CPU 密集型的核心线程数一般配置为 CPU 核数,最多加 1 作为缓冲。
IO 密集型任务,线程大部分时间都在等 IO 返回,比如数据库查询、远程 HTTP 调用,线程是阻塞着的,CPU 是空闲的。这时候多开线程完全不会增加 CPU 压力,反而能大幅提升并发吞吐。所以 IO 密集型的核心线程数通常配置为 CPU 核数的 2 倍起步,具体的上限要通过压测来决定。
这里有一个我在实际项目中总结的细节,很多人配完参数就不管了。 线程池的核心参数,尤其是 corePoolSize 和队列大小,最好不要硬编码,而是通过配置中心动态下发,这样在线上出现问题时可以实时调整,不用重启服务。
除了参数配置之外,线程池的监控机制同样重要。我们会在 beforeExecute 和 afterExecute 两个扩展点里埋点,收集每个任务的执行耗时、队列积压量、活跃线程数这些核心指标,推送到监控大盘。这样一旦线程池出现堆积或者拒绝,立刻就能在大盘上感知到。
关于拒绝策略,生产环境里我们不会用默认的 AbortPolicy 直接抛异常,而是自定义一个策略:先把被拒绝的任务写入本地内存队列做降级兜底,同时触发告警,让业务层有机会做重试或降级处理。