一句话总结:
Java 的线程池就像快递站的智能调度系统——固定员工 + 临时工 + 任务排队区 + 爆仓应急方案,保证包裹(任务)高效处理,不挤爆仓库(内存)!
一、线程池的四大核心组件
想象一个快递站的管理系统:
-
核心快递员(核心线程数) :
- 长期雇佣的正式员工,即使没包裹(任务)也留着,随时待命。
- 默认不偷懒:除非设置
allowCoreThreadTimeOut(true),否则永不裁员。
-
临时快递员(最大线程数 - 核心线程数) :
- 忙不过来时临时招的兼职,闲太久(超过
keepAliveTime)就开除。
- 忙不过来时临时招的兼职,闲太久(超过
-
包裹暂存区(任务队列) :
-
核心快递员都在忙时,新包裹先放这里排队。
-
常见队列类型:
- 无限仓库(
LinkedBlockingQueue):容易爆仓(内存溢出)! - 有限货架(
ArrayBlockingQueue):货架满了触发招临时工。
- 无限仓库(
-
-
爆仓应急方案(拒绝策略) :
-
包裹太多,货架满 + 临时工招满 → 启动应急方案:
- 拒收包裹(
AbortPolicy):直接抛异常,默认策略。 - 让寄件人自己送(
CallerRunsPolicy):谁提交任务谁自己执行。 - 丢弃最旧包裹(
DiscardOldestPolicy):把队首任务扔掉,塞新任务。 - 默默丢弃(
DiscardPolicy):直接无视新任务。
- 拒收包裹(
-
二、线程池工作流程(包裹处理流水线)
-
来新包裹(提交任务) :
- 第1步:有空闲核心快递员 → 立刻处理。
- 第2步:核心快递员全忙 → 包裹进暂存区排队。
- 第3步:暂存区满 → 招临时快递员(直到达到最大线程数)。
- 第4步:临时工也招满 → 触发爆仓应急方案!
流程图:
新任务 → 核心线程有空? → 是 → 立刻执行
↓ 否
→ 队列未满? → 是 → 入队等待
↓ 否
→ 还能招临时工? → 是 → 创建临时线程执行
↓ 否
→ 执行拒绝策略
三、线程复用秘密:快递员的摸鱼循环
快递员(线程)怎么做到一直干活不辞职?
// 简化版线程运行逻辑
while (任务 != null || 允许摸鱼) {
任务 = 队列.take(); // 从队列取任务(没任务就等)
执行任务;
}
// 如果允许摸鱼时间到,且是临时工 → 辞职走人
- 核心快递员:默认一直等任务(
queue.take()会阻塞)。 - 临时快递员:用
queue.poll(keepAliveTime)等任务,超时后自杀。
四、参数配置口诀:按需定制团队
-
CPU 密集型任务(如计算圆周率):
- 推荐:核心线程数 = CPU 核数,队列用有界队列。
- 原因:线程太多会导致频繁切换,反而降低效率。
-
IO 密集型任务(如网络请求):
- 推荐:核心线程数 = 2 * CPU 核数,最大线程数适当调高。
- 原因:线程等待 IO 时不占用 CPU,可并行更多任务。
-
混合型任务:
- 推荐:拆分为 CPU 密集和 IO 密集两部分,分别用不同线程池。
五、避坑指南:别让线程池坑了你!
-
慎用无界队列:
- 错误:
new FixedThreadPool(5)→ 默认用无界队列,任务堆积 → 内存溢出! - 正确:手动创建线程池,用有界队列(如
ArrayBlockingQueue)。
- 错误:
-
拒绝策略要合理:
- 错误:线上服务用
AbortPolicy→ 任务提交失败直接抛异常,服务崩溃! - 正确:用
CallerRunsPolicy或自定义策略(如记录日志 + 降级处理)。
- 错误:线上服务用
-
监控线程池状态:
- 关键指标:活跃线程数、队列大小、完成任务数。
- 工具:Spring Boot Actuator、自定义监控日志。
六、代码示例:手动创建安全线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数(正式员工)
10, // 最大线程数(正式+临时)
60, TimeUnit.SECONDS, // 临时工摸鱼60秒后开除
new ArrayBlockingQueue<>(100), // 有界队列(容量100)
new ThreadFactory() { // 给快递员起名字(方便排查问题)
private AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "快递员-" + count.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 爆仓了让提交任务的人自己干!
);
总结口诀:
“线程池,四件套,核心临时队拒绝。
任务提交先看核,核满入队再招人。
队列若满拒任务,参数按需别瞎配。
无界队列是炸弹,拒绝策略要谨慎!”