一、线程池的核心作用
线程池的目的是:
-
降低线程创建/销毁开销;
-
统一管理线程资源;
-
提高系统响应速度;
-
提供拒绝策略、队列管理、超时等机制。
二、核心参数详解
参数名 | 含义说明 |
---|---|
corePoolSize | 核心线程数,任务少时也会常驻(默认不回收) |
maximumPoolSize | 最大线程数,线程池可扩展到的上限 |
keepAliveTime | 非核心线程空闲等待任务的最长时间 |
workQueue | 任务队列,用于缓存等待执行的任务 |
threadFactory | 创建线程的工厂,一般自定义命名更易排查问题 |
handler | 拒绝策略,任务无法处理时的应对方式 |
三、常用任务队列(BlockingQueue
实现类)
队列类型 | 是否有界 | 是否触发线程扩容 | 拒绝策略是否有效 | 场景适配 | 注意事项 |
---|---|---|---|---|---|
ArrayBlockingQueue | ✅ 是 | ✅ 是 | ✅ 是 | 高并发/可控任务 | 容量需合理设置 |
LinkedBlockingQueue | ❌ 否 | ❌ 否 | ❌ 否 | 轻量任务 | 内存泄漏风险 |
SynchronousQueue | ❌ 实际 0 | ✅ 是 | ✅ 是 | 实时任务 | 容易拒绝 |
PriorityBlockingQueue | ❌ 否 | ❌ 否 | ❌ 否 | 调度优先级 | 与 maxPoolSize 不兼容 |
DelayQueue | ❌ 否 | ❌ 否 | ❌ 否 | 定时任务 | 要配合手动调度逻辑 |
1️⃣ ArrayBlockingQueue
(有界阻塞队列)
✅ 特性:
-
基于数组的有界 FIFO 队列
-
任务按顺序排队,线程安全(ReentrantLock)
💡 执行逻辑:
-
当核心线程用完时任务进队列
-
当队列满时才会触发线程池扩容(创建非核心线程)
-
当最大线程数也用完时,触发拒绝策略
🚀 适用场景:
-
高频任务提交,任务执行时间较长
-
需要控制内存、避免任务堆积(限流)
⚠️ 注意:
必须设置一个合理大小(如 100、1000),不能太大或太小
2️⃣ LinkedBlockingQueue
(无界阻塞队列)
✅ 特性:
-
链表结构,理论无界(默认
Integer.MAX_VALUE
)
💡 执行逻辑:
-
核心线程用完后,任务直接入队
-
不会扩容到最大线程数!
-
maxPoolSize 和拒绝策略几乎失效
🚀 适用场景:
-
任务极轻,提交频率适中(如日志异步处理)
⚠️ 注意:
-
容易引起 内存泄漏 / OOM
-
不推荐在生产中使用,除非你做了手动限流或批处理
3️⃣ SynchronousQueue
(同步队列)
✅ 特性:
-
容量为 0,任务不能排队
-
每个任务提交必须“直接交给线程处理”,否则失败
💡 执行逻辑:
-
提交任务 → 必须立即由线程来消费
-
核心线程用完后,直接创建非核心线程(迅速触发扩容)
-
没线程可用时触发拒绝策略
🚀 适用场景:
-
每个任务必须“立刻处理”,实时性强
-
高吞吐、短任务,如 Netty 或 Tomcat 默认使用
⚠️ 注意:
-
非常敏感,线程池必须具备高并发处理能力
-
否则容易大量拒绝或失败
4️⃣ PriorityBlockingQueue
(优先级阻塞队列)
✅ 特性:
-
无界,按任务
compareTo()
或Comparator
决定执行顺序
💡 执行逻辑:
-
按优先级排序出队任务
-
因为是无界队列,不会触发线程池扩容(如同
LinkedBlockingQueue
)
🚀 适用场景:
-
任务调度中心、爬虫优先级控制、服务降级处理
⚠️ 注意:
-
不支持任务“先入先出”,必须设定优先级规则
-
不推荐与
maximumPoolSize
一起使用(扩容无效)
5️⃣ DelayQueue
(延迟阻塞队列)
✅ 特性:
-
存放实现了
Delayed
接口的任务 -
任务需延迟一段时间后才能被取出执行
🚀 适用场景:
-
定时任务调度
-
延迟消息发送、订单超时处理等
⚠️ 注意:
-
和线程池搭配使用时,不能直接配合
ThreadPoolExecutor
处理并发逻辑,需要自己管理任务调度逻辑
✅ 四、设置有界队列容量的参考因素
因素 | 说明 |
---|---|
🔄 任务生产速度 | 任务是用户请求、MQ 消息、还是定时调度? |
⏱️ 任务执行时间 | 是轻量任务(几毫秒)还是重任务(几秒)? |
🧵 最大线程数 | 可并发执行多少任务? |
💾 内存资源 | 系统内存能承受多少缓冲任务? |
📈 吞吐量和延迟目标 | 是要求高吞吐还是低延迟? |
⏳ 高峰期处理能力是否需要缓冲 | 队列要不要起“高峰期间的临时缓存”作用? |
✅ 4.1、常用经验值
系统类型 | 推荐队列容量范围 |
---|---|
Web 服务请求异步处理 | 100 ~ 1000 |
消息消费服务(如 RabbitMQ/Kafka) | 1000 ~ 50000 |
数据清洗或定时计算型任务 | 2000 ~ 10000 |
日志、监控、统计类异步任务 | 5000 ~ 100000 (可容忍丢失) |
文件上传、视频处理类任务 | 100 ~ 500 |
测试环境(防止 OOM) | 10 ~ 100 |
关键提示:不要默认开太大,建议从小到大渐进调优。
🧠4.2、实战调参建议(适用于生产)
可以用以下公式估算初始值:
队列容量 ≈ (任务平均执行时间 × 任务平均到达速率) × 安全系数
举例:
-
任务平均执行时间:500ms(即每秒 2 个)
-
高峰到达速率:每秒 100 个
-
那么一秒需要缓存的任务数:
100 × 0.5 = 50
-
再乘以 2~5 倍安全系数:
50 × 4 = 200
-
👉 队列初值可以设为 200~500
✅ 建议搭配线程池运行监控(线程池活跃线程数、队列大小、拒绝次数)动态调优。
⚠️ 4.3、不要犯的常见错误
错误做法 | 风险 |
---|---|
不设上限使用无界队列 | OOM、系统雪崩 |
队列过小且不监控 | 容易触发拒绝策略,导致任务丢失 |
队列太大 + 任务耗时高 | 任务积压、长尾延迟 |
线程数和队列容量不成比例 | 不协调,性能瓶颈 |
✅4.4 最佳实践建议
-
队列设置范围:通常设置为
核心线程数 × 100 ~ 1000
; -
预估峰值任务速率后,乘以任务执行时长,再乘安全系数;
-
结合监控(如 Micrometer/Prometheus)动态调参;
-
高风险任务使用自定义拒绝策略+报警机制;
-
对延迟敏感场景,宁可触发拒绝策略也不要任务堆积。
五、四种拒绝策略(RejectedExecutionHandler
)
策略名 | 行为说明 |
---|---|
AbortPolicy (默认) | 抛出 RejectedExecutionException ,让调用者感知异常 |
DiscardPolicy | 直接丢弃任务,不报错(危险) |
DiscardOldestPolicy | 丢弃队列中最旧的任务,再尝试加入新任务 |
CallerRunsPolicy | 由提交任务的线程执行该任务,起到“背压”作用(缓冲) |
✅ 面试答法:
CallerRunsPolicy
是“缓冲过载”的策略,不会丢任务,但可能阻塞主线程。
六、线程池执行流程(关键逻辑)
-
有任务进来,线程池看当前线程数:
-
少于
corePoolSize
→ 创建新线程执行; -
多于
corePoolSize
→ 任务进队列; -
如果队列满了 → 尝试创建新线程(不超过
maximumPoolSize
); -
若线程已达最大,任务仍无法执行 → 启动拒绝策略。
-
七、线程复用机制重点讲解
🌟 线程池始终优先复用空闲线程,而不是盲目新建线程
-
哪怕当前线程池中线程数 <
corePoolSize
,如果有空闲线程,也会优先复用; -
只有当所有线程都在忙,才会新建线程;
-
这是为了避免线程频繁创建/销毁带来的性能开销。
八、核心线程 vs 最大线程区别
对比项 | 核心线程 corePoolSize | 最大线程 maximumPoolSize |
---|---|---|
生命周期 | 默认永不销毁 | 空闲超时后自动销毁 |
创建时机 | 任务来就会启动 | 核心线程+队列满后才扩展 |
适用场景 | 稳定长期处理任务 | 应对突发流量 |
🎯 面试总结语:核心线程是常驻兵,最大线程是应急兵。
九、实战注意事项(项目中很重要)
✅ 合理设置线程数
corePoolSize = CPU核心数 + 1 (IO密集型)
maximumPoolSize = 2 * CPU核心数 + 1
✅ 使用有界队列
避免 LinkedBlockingQueue
默认 Integer.MAX_VALUE,造成 OOM。
✅ 自定义 ThreadFactory
设置线程名前缀,便于日志排查和线程管理。
✅ 线程池不要随意关闭
除非业务中明确生命周期,否则不要使用 shutdownNow()
强行终止线程。
✅ 使用监控工具/埋点
监控线程池活跃线程数、队列长度、拒绝次数等。
✅ 合理选择拒绝策略
推荐:
-
Web/任务调度服务:
AbortPolicy
或CallerRunsPolicy
-
日志/告警/异步推送服务:
CallerRunsPolicy
或DiscardOldestPolicy
十、一句话总结线程池运行流程(适合面试快速答):
Java 线程池执行任务时,优先使用核心线程,其次是任务队列,再扩展最大线程数,最后使用拒绝策略兜底,并始终优先复用空闲线程资源。