讲讲线程池的实现原理
当提交一个新任务到线程池时,线程池的处理流程如下:
- 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
- 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
- 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。
三步流程是 ThreadPoolExecutor.execute 的行为描述,但实现层面真正精妙的是:一个 int 变量(ctl)同时编码了线程池状态与线程数,以及”Worker 线程自取任务”的运行模型。本文把流程落到源码机制,并讲清拒绝策略与生命周期。
ctl:一个原子整数的双重编码
ThreadPoolExecutor 用 AtomicInteger ctl 的高 3 位表示池状态(RUNNING / SHUTDOWN / STOP / TIDYING / TERMINATED),低 29 位表示线程数。一次 CAS 即可原子地同时判断与修改”状态+数量”,避免了双变量同步的复杂性——这是整个实现的地基。
execute 的源码级三步
public void execute(Runnable command) { |
细节修正一个常见误解:步骤 1 新建线程时任务直接交给新线程执行(firstTask),并不入队;只有核心线程满后任务才进队列。所以”核心线程空闲时从队列取任务”的说法不准确——空闲核心线程在 getTask 里阻塞等队列,新任务优先触发建线程直到 corePoolSize。
Worker 运行模型:线程如何”自取任务”
Worker 是包着 Thread 的 Runnable,其 run 循环:
- 执行 firstTask(若有)
- 之后循环
getTask():从 workQueue 取任务(核心线程 take 阻塞等待;非核心线程 poll 带超时,超时未取到则返回 null → 该 Worker 退出,实现非核心线程回收) - 取到任务就 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 自取任务的循环模型、队列作为背压缓冲、拒绝策略作为最后防线。理解这条主线后,七个构造参数的每一个都有了明确的存在理由。











