Skip to content

线程池执行流程原理

线程池最容易被误解的点是执行流程。很多人以为任务来了会优先创建到最大线程数,其实 ThreadPoolExecutor 的核心流程是:先核心线程,再队列,再最大线程,最后拒绝策略

execute 总流程

mermaid
flowchart TD
    A["调用 execute 提交任务"] --> B{"运行线程数小于 corePoolSize"}
    B -- "是" --> C["创建核心 Worker 执行任务"]
    B -- "否" --> D{"线程池是否运行中"}
    D -- "否" --> E["拒绝任务"]
    D -- "是" --> F{"任务能否放入队列"}
    F -- "是" --> G["任务进入 workQueue"]
    G --> H["Worker 从队列取任务执行"]
    F -- "否" --> I{"运行线程数小于 maximumPoolSize"}
    I -- "是" --> J["创建非核心 Worker 执行任务"]
    I -- "否" --> K["执行拒绝策略"]

这个流程解释了一个非常重要的现象:如果你使用无界队列,队列永远不容易满,那么 maximumPoolSize 基本不会生效。

为什么流程这样设计

阶段设计目的如果没有这一层
核心线程保留基础处理能力每个任务都排队或临时建线程
队列吸收短时间流量波动流量稍微上涨就创建大量线程
最大线程队列也扛不住时临时扩容峰值只能排队,延迟上升
拒绝策略系统满载时保护自己无限堆积,拖垮 JVM 和下游

线程池不是为了让所有任务立刻执行,而是为了在吞吐、延迟、资源和稳定性之间做平衡。

Worker 是什么

Worker 可以理解为线程池里的工作单元:它内部持有一个线程,并负责执行第一个任务,然后循环从队列取后续任务。

mermaid
flowchart TD
    A["Worker 创建"] --> B["执行 firstTask"]
    B --> C["进入 runWorker 循环"]
    C --> D["从队列 getTask"]
    D --> E{"是否取到任务"}
    E -- "是" --> F["执行任务"]
    F --> C
    E -- "否" --> G["Worker 退出并回收"]

所以线程池不是“一个任务一个线程”,而是“一个 Worker 线程不断消费多个任务”。

JDK 7/8 中要重点理解的点

线程池是 JDK 5 引入并在 JDK 7/8 中大量使用的核心并发工具。JDK 8 虽然增加了 CompletableFuture 和并行 Stream,但底层仍离不开线程池。

能力JDK 7 常见写法JDK 8 常见写法
无返回值异步ExecutorService.execute仍然可用
有返回值异步submit + FutureCompletableFuture 更方便组合
并行集合处理手写线程池拆任务parallelStream,但要小心公共线程池
多任务聚合多个 Future 手动 getCompletableFuture.allOf

不要以为 JDK 8 有了 CompletableFuture 就不需要线程池。CompletableFuture 只是任务编排工具,真正执行任务的还是线程池。

execute 和 submit 的区别

mermaid
flowchart TD
    A["提交任务"] --> B{"使用 execute"}
    A --> C{"使用 submit"}
    B --> D["只提交 Runnable"]
    D --> E["异常直接进入线程异常处理"]
    C --> F["包装成 FutureTask"]
    F --> G["异常保存在 Future 中"]
    G --> H["调用 get 时抛出 ExecutionException"]

区别:

对比executesubmit
返回值返回 Future
异常表现任务异常可能直接打印或由异常处理器处理异常被包装,调用 get() 才能看到
适合场景不关心结果的任务需要结果、取消、等待的任务

错误示例:

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SubmitExceptionDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        pool.submit(() -> {
            throw new RuntimeException("任务失败");
        });

        pool.shutdown();
    }
}

这段代码可能不会在主线程直接看到异常,因为异常被保存在 Future 里了。

正确写法:

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SubmitExceptionRightDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        Future<?> future = pool.submit(() -> {
            throw new RuntimeException("任务失败");
        });

        try {
            future.get();
        } finally {
            pool.shutdown();
        }
    }
}

线程池状态如何影响执行

线程池不是只有“运行”和“停止”两个状态。

mermaid
flowchart TD
    A["RUNNING"] --> B["SHUTDOWN"]
    A --> C["STOP"]
    B --> D["TIDYING"]
    C --> D
    D --> E["TERMINATED"]
状态含义
RUNNING接收新任务,也处理队列任务
SHUTDOWN不接收新任务,但继续处理队列任务
STOP不接收新任务,不处理队列任务,并中断正在执行的任务
TIDYING所有任务结束,准备执行终止钩子
TERMINATED完全终止

如果调用 shutdown() 后还继续提交任务,就会触发拒绝策略。

商业场景:接口聚合

订单详情页可能需要并行查询订单、支付、物流、优惠券。线程池执行流程决定了接口在高峰期会怎么表现。

java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class OrderQueryPoolDemo {
    private static final ThreadPoolExecutor QUERY_POOL = new ThreadPoolExecutor(
            8,
            16,
            30,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(200),
            r -> new Thread(r, "order-query-" + System.nanoTime()),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public static void main(String[] args) {
        QUERY_POOL.execute(() -> System.out.println("查询订单"));
        QUERY_POOL.execute(() -> System.out.println("查询支付"));
        QUERY_POOL.execute(() -> System.out.println("查询物流"));
        QUERY_POOL.shutdown();
    }
}

如果下游支付接口很慢,线程会被占住。此时队列会积压,最后触发拒绝或反压。真正的生产配置还要加超时、熔断、限流和下游隔离。

面试标准回答

问题:线程池任务提交后的执行流程是什么?

标准回答:

任务通过 execute 提交后,线程池会先判断当前工作线程数是否小于核心线程数,如果小于就创建核心线程执行;否则尝试把任务放入阻塞队列;如果队列满了,再判断是否能创建到最大线程数;如果最大线程数也满了,就执行拒绝策略。这个流程说明线程池不是优先创建最大线程,而是先使用核心线程和队列来稳定处理任务。

本章小结

线程池执行流程要和参数一起理解。无界队列会让最大线程数失效,submit 会隐藏任务异常,shutdown 后继续提交会触发拒绝。真正掌握线程池,必须知道任务从提交、入队、执行、异常、关闭到回收的完整链路。