Skip to content

概念卡片: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 线程抛出,可自定义 afterExecuteThread.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开发手册》+ 源文总结)

  1. 【强制】手动 new ThreadPoolExecutor,禁用 Executors.newFixedThreadPool/newCachedThreadPool(前者无界队列、后者无限线程,都 OOM 风险)
  2. 【强制】线程要有意义的命名(threadFactory),出错好回溯
  3. 【建议】不同业务用不同线程池(隔离影响)
  4. 线程数公式:CPU 密集型 N+1(N=核数,+1 兜缺页中断);I/O 密集型 2N
  5. 【建议】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());

常见误解(避坑)

  1. ❌ "线程池创建后立即建好核心线程"。→ 懒加载,有任务提交才建线程;除非 prestartCoreThread()/prestartAllCoreThreads()
  2. ❌ "Executor 结尾的都是线程池"。→ SimpleAsyncTaskExecutor 每次新开线程、SyncTaskExecutor 在原线程执行,都不是池;Executor 接口只是"将来执行命令",线程池是 ThreadPoolExecutor
  3. ❌ "核心线程永远不会销毁"。→ JDK1.6 后 allowsCoreThreadTimeOut=true 时闲置核心线程也会终止。
  4. ❌ "submit 提交的任务异常会被线程池兜住"。→ 异常被 FutureTask 封装进 outcome,必须在 get() 里 catch,否则静默丢失。

关联

最近更新