当提交一个新任务到线程池时,线程池的处理流程如下:

  1. 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。

三步流程是 ThreadPoolExecutor.execute 的行为描述,但实现层面真正精妙的是:一个 int 变量(ctl)同时编码了线程池状态与线程数,以及”Worker 线程自取任务”的运行模型。本文把流程落到源码机制,并讲清拒绝策略与生命周期。

ctl:一个原子整数的双重编码

ThreadPoolExecutor 用 AtomicInteger ctl 的高 3 位表示池状态(RUNNING / SHUTDOWN / STOP / TIDYING / TERMINATED),低 29 位表示线程数。一次 CAS 即可原子地同时判断与修改”状态+数量”,避免了双变量同步的复杂性——这是整个实现的地基。

execute 的源码级三步

public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) // 步骤1
if (addWorker(command, true)) return; // 新建核心线程,任务作为首任务
if (isRunning(c) && workQueue.offer(command)) { // 步骤2
// 入队后二次检查:池已关闭则移除并拒绝;无线程则补一个
} else if (!addWorker(command, false)) // 步骤3
reject(command); // 非核心也满 → 拒绝
}

细节修正一个常见误解:步骤 1 新建线程时任务直接交给新线程执行(firstTask),并不入队;只有核心线程满后任务才进队列。所以”核心线程空闲时从队列取任务”的说法不准确——空闲核心线程在 getTask 里阻塞等队列,新任务优先触发建线程直到 corePoolSize。

Worker 运行模型:线程如何”自取任务”

Worker 是包着 Thread 的 Runnable,其 run 循环:

  1. 执行 firstTask(若有)
  2. 之后循环 getTask():从 workQueue 取任务(核心线程 take 阻塞等待;非核心线程 poll 带超时,超时未取到则返回 null → 该 Worker 退出,实现非核心线程回收)
  3. 取到任务就 runWorker 执行,异常处理保证线程不因任务异常而误退(非核心除外)

“线程池复用线程”的真相就是:线程不退出,在 getTask 上阻塞等下一个任务。队列是任务与线程之间的解耦缓冲。

队列与拒绝策略

workQueue 选型决定池的行为性格:

  • LinkedBlockingQueue(无界):任务只增不消时内存溢出风险(fixedThreadPool 默认)
  • ArrayBlockingQueue(有界):背压明确,满则触发非核心线程/拒绝,生产推荐
  • SynchronousQueue:不存任务,直接握手交给线程(cachedThreadPool),适合短平快任务
  • PriorityBlockingQueue:优先级调度

队列满且线程达 maximumPoolSize 后触发拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(调用者线程执行,天然降速)、DiscardPolicy(静默丢)、DiscardOldestPolicy(丢队头)。生产常用 CallerRunsPolicy 或自定义策略(落库/告警后丢弃)。

生命周期与优雅关闭

  • shutdown:不再接新任务,等已提交任务(含队列中)执行完
  • shutdownNow:中断进行中的任务,清空队列返回未执行任务
  • 状态迁移:RUNNING → SHUTDOWN(shutdown 触发)→ TIDYING(线程数归 0)→ TERMINATED(terminated 钩子)

参数配置的工程依据

  • CPU 密集:N+1(N=核数),线程多了只是切换浪费
  • IO 密集:N × (1 + 等待时间/计算时间),或用压测定容量;更稳的做法是队列有界 + 监控队列深度动态调整
  • 核心 vs 最大:突发流量场景 core 小、max 大、队列中等;平稳场景 core=max 减少抖动

线程池原理的主线:ctl 单变量编码状态、Worker 自取任务的循环模型、队列作为背压缓冲、拒绝策略作为最后防线。理解这条主线后,七个构造参数的每一个都有了明确的存在理由。