主题
概念卡片:Java 线程池 ThreadPoolExecutor
一句话机制
ThreadPoolExecutor 用「核心线程 + 工作队列 + 最大线程 + 拒绝策略」四要素复用线程:提交任务时,先看核心线程够不够,再看队列放不放得下,最后才扩到最大线程,都不行就拒绝。 核心线程靠 queue.take() 阻塞保活,非核心线程靠 queue.poll(keepAliveTime) 超时回收。
execute() 执行三步(源码核心)
java
// 1. 线程数 < corePoolSize → 新建线程执行
if (workerCountOf(c) < corePoolSize) { addWorker(command, true); }
// 2. 能入队 → 入队(再复查:池停了要回滚;线程数为 0 要补一个 worker)
else if (isRunning(c) && workQueue.offer(command)) { ... }
// 3. 入队失败(队列满)→ 未达 maximumPoolSize 则新建,否则拒绝
else if (!addWorker(command, false)) { reject(command); }| 顺序 | 判断 | 动作 |
|---|---|---|
| 1 | 线程数 < corePoolSize | 新建核心线程执行 |
| 2 | 队列未满 | 任务入队等待 |
| 3 | 线程数 < maximumPoolSize | 新建非核心线程执行 |
| 4 | 队列满 + 线程满 | 走拒绝策略 |
关键参数与保活机制
| 参数 | 含义 | 要点 |
|---|---|---|
| corePoolSize | 核心线程数 | 空闲也默认不回收 |
| maximumPoolSize | 最大线程数 | 只在队列满后才会扩到 |
| keepAliveTime | 非核心线程空闲存活时间 | 配合 workQueue.poll(keepAliveTime) |
| workQueue | 工作队列 | 必须有界,防 OOM |
| threadFactory | 线程工厂 | 必须给线程有意义的名字 |
| handler | 拒绝策略 | AbortPolicy 等 |
保活 vs 回收的核心区别:核心线程用
workQueue.take()(阻塞,一直等);非核心线程用workQueue.poll(keepAliveTime, NANOSECONDS)(等 keepAliveTime 取不到任务就 return null,Worker 循环结束、线程终止)。
两个易混配置
corePoolSize=0:平时只用 1 个线程消费任务,队列满了才多线程。JDK1.6 后优化:提交时池空会立即建线程(先排队再取),不必等队列满。allowsCoreThreadTimeOut=true:允许闲置的核心线程也终止,效果近似corePoolSize=0,但实现细节不同(一个是「只保留非核心」,一个是「核心也可超时回收」)。
submit vs execute 的异常处理
- execute:异常在 Worker 线程抛出,可自定义
afterExecute或Thread.UncaughtExceptionHandler捕获 - submit:底层也是 execute,但
FutureTask.run()内部把异常try-catch封装进outcome属性,Worker 拿不到异常,须在future.get()处 catch
不变量:submit 的异常要
get()时才能拿到(FutureTask.report()抛 ExecutionException),execute 的异常要么任务内 try-catch,要么用 afterExecute/UncaughtExceptionHandler。
shutdown vs shutdownNow
| 方法 | 行为 |
|---|---|
shutdown() | 平缓关闭,等已入队任务执行完 |
shutdownNow() | 立即关闭,中断正在执行的任务,返回未执行任务列表 |
最佳实践(源自《Java开发手册》+ 源文总结)
- 【强制】手动
new ThreadPoolExecutor,禁用Executors.newFixedThreadPool/newCachedThreadPool(前者无界队列、后者无限线程,都 OOM 风险) - 【强制】线程要有意义的命名(threadFactory),出错好回溯
- 【建议】不同业务用不同线程池(隔离影响)
- 线程数公式:CPU 密集型
N+1(N=核数,+1 兜缺页中断);I/O 密集型2N - 【建议】workQueue 用有界队列,避免任务堆积 OOM
java
// 4C8G、I/O 密集、任务 100~300ms 的推荐实例
new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(512),
new ThreadFactoryBuilder().setNameFormat("po-detail-pool-%d").build(),
new ThreadPoolExecutor.AbortPolicy());常见误解(避坑)
- ❌ "线程池创建后立即建好核心线程"。→ 懒加载,有任务提交才建线程;除非
prestartCoreThread()/prestartAllCoreThreads()。 - ❌ "Executor 结尾的都是线程池"。→
SimpleAsyncTaskExecutor每次新开线程、SyncTaskExecutor在原线程执行,都不是池;Executor接口只是"将来执行命令",线程池是ThreadPoolExecutor。 - ❌ "核心线程永远不会销毁"。→ JDK1.6 后
allowsCoreThreadTimeOut=true时闲置核心线程也会终止。 - ❌ "submit 提交的任务异常会被线程池兜住"。→ 异常被
FutureTask封装进outcome,必须在get()里 catch,否则静默丢失。
关联
- 原始资料:✅线程池10个问题 · ✅开发实战:合理使用线程池和线程变量
- 总览:并发编程技术栈总览
- 相关卡:概念卡片:AQS同步器框架
- 域地图:A00-百科/Java后端/Java后端