《Java 100 天进阶之路》第55篇:线程池ThreadPoolExecutor(2026版)

📅 2026/7/13 10:14:49 👁️ 阅读次数 📝 编程学习
《Java 100 天进阶之路》第55篇:线程池ThreadPoolExecutor(2026版)

第55篇:线程池ThreadPoolExecutor(2026版)

📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第54篇:AQS抽象队列同步器 |
➡️ 下一篇:第56篇:死锁与排查工具(待发布

🗺️ 本文阅读地图(3 分钟速览)

第54篇拿下了 AQS,本篇深入并发编程最常用的工具——线程池。掌握了 ThreadPoolExecutor,你就掌握了 Java 并发编程的“生产力工具”:

模块核心问题一句话回答
七大参数线程池由哪些参数控制?corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler
工作流程任务提交后怎么处理?核心线程 → 任务队列 → 非核心线程 → 拒绝策略
四种拒绝策略线程池满了怎么办?AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最旧)
Executors 陷阱为什么大厂禁止用 Executors?无界队列 → OOM,无限线程 → 资源耗尽
面试最爱问高频考点有哪些?见文末 🎤 小节

文章目录

    • 第55篇:线程池ThreadPoolExecutor(2026版)
      • 🗺️ 本文阅读地图(3 分钟速览)
      • 一、核心知识点
      • 二、生活类比:从“互联网公司”到“线程池”
      • 三、七大核心参数详解
        • 3.1 corePoolSize(核心线程数)
        • 3.2 maximumPoolSize(最大线程数)
        • 3.3 keepAliveTime + unit(空闲存活时间)
        • 3.4 workQueue(工作队列)
        • 3.5 threadFactory(线程工厂)
        • 3.6 handler(拒绝策略)
      • 四、任务执行流程(面试核心)
        • 4.1 完整流程图
        • 4.2 关键理解
        • 4.3 execute() vs submit()
        • 4.4 线程池生命周期管理
      • 五、Executors 的陷阱(为什么大厂禁用)
        • 5.1 三大陷阱
        • 5.2 正确做法:手动创建 ThreadPoolExecutor
      • 六、生产级避坑清单
      • 七、面试高频考点
      • 面试官追问陷阱(加分题)
      • 八、练习题
    • 📊 你的学习进度
    • 👉 下一篇文章预告

一、核心知识点

线程池是什么?

线程池是一种池化资源管理技术,通过预先创建一定数量的线程并统一管理,实现任务的异步执行与资源复用。

四大核心优势

优势说明
降低资源消耗复用已创建的线程,避免频繁创建/销毁的系统开销
提高响应速度任务到达时无需等待线程创建即可执行
提高线程可管理性统一分配、调优和监控线程资源
提供附加功能定时执行、并发控制、任务排队等

核心设计思想:基于生产者-消费者模型——提交任务的线程是生产者,线程池中的工作线程是消费者,任务队列(BlockingQueue)是缓冲区。

七大核心参数

publicThreadPoolExecutor(intcorePoolSize,// 1. 核心线程数intmaximumPoolSize,// 2. 最大线程数longkeepAliveTime,// 3. 空闲线程存活时间TimeUnitunit,// 4. 时间单位BlockingQueue<Runnable>workQueue,// 5. 工作队列ThreadFactorythreadFactory,// 6. 线程工厂RejectedExecutionHandlerhandler// 7. 拒绝策略)

二、生活类比:从“互联网公司”到“线程池”

线程池就像一家互联网公司的人力资源管理

  • corePoolSize(核心线程数):公司的正式员工。只要公司不倒闭,即使没活干也不会轻易辞退。
  • maximumPoolSize(最大线程数):公司能容纳的总人数(正式员工 + 临时工)。当正式员工都在忙,任务积压时,公司会招聘临时工。
  • workQueue(任务队列):公司的待办任务池。正式员工忙不过来时,新任务先放进待办池排队。
  • 非核心线程(临时工):任务池满了,正式员工忙不过来,公司开始招临时工帮忙。任务高峰期过去,临时工空闲太久就会被裁掉(keepAliveTime)。
  • 拒绝策略:正式员工、临时工都在忙,待办任务池也满了,新任务来了怎么办?公司有四种应对方案(见下文)。

线程池初始化时不会立即创建核心线程,而是在任务提交时懒加载创建,正式员工是“入职后才开始干活”。


三、七大核心参数详解

3.1 corePoolSize(核心线程数)

定义:线程池中长期维持的最小线程数量,即使空闲也不会被销毁。

核心行为

  • 默认情况下,核心线程会一直存活
  • 提交新任务时,如果当前运行线程数 < corePoolSize,立即创建新线程执行任务(即使其他核心线程空闲)
  • 可通过allowCoreThreadTimeOut(true)允许核心线程超时销毁

设置建议

  • CPU 密集型(计算为主):CPU 核数 + 1
  • IO 密集型(网络/磁盘等待为主):CPU 核数 * 2CPU 核数 / (1 - 阻塞系数)
3.2 maximumPoolSize(最大线程数)

定义:线程池允许创建的最大线程数量。

核心行为

  • 核心线程已满工作队列已满时,线程池会创建非核心线程直到达到 maximumPoolSize
  • 包含核心线程和非核心线程两部分

注意事项

  • maximumPoolSize 必须 >= corePoolSize
  • 如果队列是无界队列(如LinkedBlockingQueue默认),该参数失效(因为队列永远不会满)
3.3 keepAliveTime + unit(空闲存活时间)

定义:非核心线程空闲后的存活时间。

核心行为

  • 当线程池中的线程数量超过 corePoolSize 时,多余的空闲线程在终止前等待新任务的最长时间
  • 若设置allowCoreThreadTimeOut(true),核心线程也会受此参数影响
3.4 workQueue(工作队列)

定义:用于存储等待执行任务的阻塞队列。

常见队列对比

队列类型特点适用场景风险点
ArrayBlockingQueue有界阻塞队列,基于数组,FIFO任务量可控,需限制队列长度需合理设置队列大小
LinkedBlockingQueue可选有界(默认无界),基于链表流量平稳的后台任务无界队列 → 内存溢出风险
SynchronousQueue不存储元素,直接交给线程高吞吐、低延迟场景需配合足够线程数
PriorityBlockingQueue支持优先级的无界阻塞队列需按优先级执行任务无界队列 → OOM 风险
DelayQueue延迟队列,元素到期后才能被获取定时任务、超时处理无界队列 → OOM 风险

生产环境建议

  • 避免使用无界队列,推荐设置队列容量为max(核心线程数, 预期峰值 QPS × 平均处理时间)
  • 对于突发流量场景,可采用SynchronousQueue+ 弹性扩容策略
3.5 threadFactory(线程工厂)

定义:创建线程的工厂。

核心作用

  • 默认是Executors.defaultThreadFactory()
  • 自定义时可设置线程名称、是否为守护线程等

自定义示例

ThreadFactorynamedFactory=r->newThread(r,"biz-pool-%d");// 或使用 GuavaThreadFactorynamedFactory=newThreadFactoryBuilder().setNameFormat("order-process-pool-%d").build();
3.6 handler(拒绝策略)

定义:当线程池已满(队列满了 + 线程数已达 maximum)且还有新任务时触发的处理策略。

四种内置拒绝策略

策略行为适用场景
AbortPolicy抛出RejectedExecutionException默认需要明确感知任务被拒绝
CallerRunsPolicy调用者线程执行该任务不能丢失任务,可接受调用者阻塞
DiscardPolicy静默丢弃新任务,不抛异常可容忍部分任务丢失(如日志)
DiscardOldestPolicy丢弃队列中最旧的任务,再提交新任务需要最新任务优先执行

💡AbortPolicy 是默认策略,如果不想任务丢失,应显式设置CallerRunsPolicy


四、任务执行流程(面试核心)

4.1 完整流程图

线程池执行execute()四步流程

4.2 关键理解

⚠️重要:队列满了才创建非核心线程,而不是先扩充到 maximumPoolSize。

设计逻辑:核心线程是“常驻员工”,任务是“待办池”,非核心线程是“临时工”。只有待办池满了,正式员工忙不过来,才招临时工。这避免了过度创建线程带来的资源浪费。

边界情况

  • corePoolSize = maximumPoolSize时,退化为固定大小线程池
  • 设置maximumPoolSizeInteger.MAX_VALUE时,允许无限扩容(需谨慎使用)
  • 无界队列时,maximumPoolSize参数失效(队列永远不会满)
4.3 execute() vs submit()
对比execute(Runnable)submit(Callable/Runnable)
返回值❌ 无返回值✅ 返回Future<T>
异常处理任务异常直接抛出到控制台异常封装在 Future 中,调用get()时抛出
适用场景不需要返回结果的任务需要获取执行结果或处理异常
4.4 线程池生命周期管理
方法说明
shutdown()优雅关闭——等待已提交任务完成,不再接受新任务
shutdownNow()立即关闭——尝试中断运行中任务,返回未执行任务列表
isShutdown()判断是否已关闭
isTerminated()判断是否已终止
awaitTermination(timeout, unit)阻塞等待线程池终止

五、Executors 的陷阱(为什么大厂禁用)

阿里巴巴《Java开发手册》明确禁止在生产环境使用Executors工厂方法创建线程池。

5.1 三大陷阱
Executors 方法隐藏的问题后果
newFixedThreadPool(n)使用无界 LinkedBlockingQueue(容量Integer.MAX_VALUE任务堆积 →OOM
newSingleThreadExecutor()同上,且单线程处理积压风险更高 →OOM
newCachedThreadPool()最大线程数Integer.MAX_VALUE,无限创建线程高并发下创建海量线程 →资源耗尽 / CPU 100%
newScheduledThreadPool(n)同样存在线程数失控风险OOM 或资源耗尽

⚠️结论:Executors 的便利背后是隐患——“无界队列 + 固定线程 = 任务无限堆积 → OOM”,“无界线程数 = 系统资源被耗尽 → OOM 或 CPU 打满”。

5.2 正确做法:手动创建 ThreadPoolExecutor
ExecutorServiceexecutor=newThreadPoolExecutor(10,// corePoolSize20,// maximumPoolSize60L,TimeUnit.SECONDS,// keepAliveTimenewArrayBlockingQueue<>(100),// ✅ 有界队列,防止OOMnewThreadFactoryBuilder()// ✅ 自定义线程名.setNameFormat("biz-pool-%d").build(),newThreadPoolExecutor.AbortPolicy()// ✅ 明确拒绝策略);

六、生产级避坑清单

✅ 线程池生产环境使用规范 1. 禁止使用 Executors 创建线程池 → 手动创建 ThreadPoolExecutor 2. 必须使用有界队列 → ArrayBlockingQueue 或指定容量的 LinkedBlockingQueue 3. 自定义线程名称 → ThreadFactory 设置线程名,便于日志追踪和问题排查 4. 根据场景选择合适的拒绝策略 → 不能丢任务用 CallerRunsPolicy 5. 线程数配置参考: - CPU 密集型:CPU 核数 + 1 - IO 密集型:CPU 核数 * 2 或更高 6. 务必在 finally 中关闭线程池 → 使用 shutdown() + awaitTermination() 7. 任务中必须捕获异常 → 否则异常会丢失,线程被回收

七、面试高频考点

Q1:ThreadPoolExecutor 的七大参数是什么?各自的作用?

corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲存活时间)、unit(时间单位)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)。核心线程是“常驻员工”,非核心线程是“临时工”,工作队列是“待办池”。

Q2:任务提交后线程池的执行流程?

三步判断:① 当前线程数 < corePoolSize → 创建核心线程执行;② 队列未满 → 放入队列等待;③ 线程数 < maximumPoolSize → 创建非核心线程执行;④ 否则执行拒绝策略。队列满了才创建非核心线程

Q3:为什么禁止使用 Executors 创建线程池?

newFixedThreadPoolnewSingleThreadExecutor使用无界 LinkedBlockingQueue,任务堆积会导致OOMnewCachedThreadPool最大线程数为Integer.MAX_VALUE,高并发下会创建海量线程,导致系统资源耗尽或 CPU 100%。生产环境必须手动创建 ThreadPoolExecutor,使用有界队列

Q4:线程池的拒绝策略有哪些?

四种内置策略:① AbortPolicy(抛异常,默认);② CallerRunsPolicy(调用者线程执行,可防止任务丢失);③ DiscardPolicy(静默丢弃);④ DiscardOldestPolicy(丢弃最旧任务)。

Q5:corePoolSize 和 maximumPoolSize 如何设置?

CPU 密集型:corePoolSize = CPU 核数 + 1,减少上下文切换。IO 密集型:corePoolSize = CPU 核数 * 2 或更高,利用等待 IO 的时间片。maximumPoolSize 根据业务峰值和系统资源调整,一般为核心线程数的 2-4 倍。必须配合有界队列,否则 maximumPoolSize 失效


面试官追问陷阱(加分题)

追问1:“LinkedBlockingQueue无界时,maximumPoolSize还有意义吗?”

👉没有意义。无界队列永远不会满,线程池永远不会创建非核心线程。maximumPoolSize和拒绝策略都失效,只有 corePoolSize 在工作。这是Executors.newFixedThreadPool()的致命问题。

追问2:“CallerRunsPolicy有什么副作用?什么时候用?”

👉 调用者线程执行任务,会阻塞调用者,可能导致上游系统超时。适用于不能丢失任务的场景(如支付回调、订单确认),且调用者能承受阻塞。

追问3:“线程池中的线程抛异常会怎样?”

👉 任务抛出未捕获异常时,线程会被回收(Worker 退出),线程池会创建新线程补充。这也是为什么需要在任务中自行捕获异常——否则任务失败不会重试,且线程创建销毁有额外开销。


八、练习题

  1. 分析题:用Executors.newFixedThreadPool(10)创建线程池,提交大量任务后为什么会 OOM?

    💡 思路:newFixedThreadPool底层使用无界 LinkedBlockingQueue,任务处理速度跟不上提交速度时,任务不断在队列中堆积,最终耗尽堆内存。

  2. 代码题:手动创建一个线程池,要求:核心线程数 5、最大线程数 20、空闲存活 60 秒、有界队列容量 100、拒绝策略为 CallerRunsPolicy、自定义线程名“biz-pool-%d”。

  3. 场景设计:某支付系统需要异步处理订单,任务绝对不能丢失,且系统对延迟不敏感。应该选择什么拒绝策略?为什么?

    💡 思路:选择CallerRunsPolicy。调用者线程直接执行任务,确保任务不会丢失。虽然会阻塞调用者,但对延迟不敏感的系统可以接受。


📊 你的学习进度

  • 当前:第55篇 / 共108篇 ·进阶篇:并发编程与JUC详解(第51~60篇)
  • ✅ 已完成:基础篇44篇 + 第45~55篇
  • 📖 正在学:第55篇
  • ⏳ 待学习:第56~108篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

👉 下一篇文章预告

🚀下一篇:《第56篇:死锁与排查工具》

内容简介:死锁的四大必要条件、jstackjconsole排查工具、代码示例、预防与避免策略、生产环境实战案例。

👉多线程专题持续深入,拿下死锁排查!

📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!