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