线程池有几种创建方式?各自适合什么场景?

#Java#面试八股

线程池有几种创建方式?各自适合什么场景?

结论先行:线程池的创建入口有四种——Executors 工厂方法、ThreadPoolExecutor 构造器、Spring 的 ThreadPoolTaskExecutor、JDK 21 的虚拟线程执行器。它们最终都落到同一个 ThreadPoolExecutor 核心,区别只在谁能把参数说清楚。

1. 四种入口

入口一句话风险
Executors.newXxx()一行搞定,参数被隐藏无界队列或无界线程数,生产禁用
new ThreadPoolExecutor(...)七个参数全部显式长,但可控、可监控
ThreadPoolTaskExecutorSpring 封装,便于 @Async 注入默认 queueCapacity=Integer.MAX_VALUE,同样是坑
Executors.newVirtualThreadPerTaskExecutor()一任务一虚拟线程只适合 IO 密集,且要限制下游并发

2. 家族关系

Executor(接口,只有 execute 一个方法)
└── ExecutorService(接口:submit / invokeAll / shutdown)
    ├── ThreadPoolExecutor            最常用,七个参数全暴露
    │   └── ScheduledThreadPoolExecutor   定时/周期任务
    ├── ForkJoinPool                  工作窃取,commonPool 也是它
    └── 以上实例都可以由 Executors 工厂方法产出
        └── Spring 再包一层:ThreadPoolTaskExecutor

3. 三种推荐写法

// 写法一:直接构造(最可控,推荐)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
        8,                                   // corePoolSize
        16,                                  // maximumPoolSize
        60, TimeUnit.SECONDS,                // 空闲回收时间
        new ArrayBlockingQueue<>(500),       // 有界队列
        new ThreadFactory() {
            private final AtomicInteger seq = new AtomicInteger(1);
            @Override public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "order-pool-" + seq.getAndIncrement());
                t.setDaemon(false);
                return t;
            }
        },
        new ThreadPoolExecutor.CallerRunsPolicy());

// 写法二:Spring 环境(配合 @Async 使用)
@Bean("orderExecutor")
public ThreadPoolTaskExecutor orderExecutor() {
    ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
    ex.setCorePoolSize(8);
    ex.setMaxPoolSize(16);
    ex.setQueueCapacity(500);                // 必须显式设置,否则无界
    ex.setThreadNamePrefix("order-");
    ex.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    ex.setWaitForTasksToCompleteOnShutdown(true);
    ex.setAwaitTerminationSeconds(30);
    ex.initialize();
    return ex;
}

// 写法三:JDK 21 虚拟线程(IO 密集、任务量大)
try (ExecutorService vt = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request req : requests) {
        vt.submit(() -> handle(req));
    }
}

4. 场景对照

场景选择关键参数
核心交易链路ThreadPoolExecutor小 core、有界队列、CallerRunsPolicy
旁路任务(发短信、写日志)独立小池队列可小,拒绝可丢弃 + 告警
定时任务ScheduledThreadPoolExecutor注意任务抛异常会终止后续调度
并行计算ForkJoinPool分治任务,工作窃取
高并发 IO(网关、爬虫)虚拟线程执行器配合 Semaphore 限流下游
Spring 异步方法ThreadPoolTaskExecutor必须显式 queueCapacity

5. 常见追问

追问一:ThreadPoolTaskExecutor 和 ThreadPoolExecutor 是什么关系? 前者是 Spring 的包装类,实现 AsyncTaskExecutor,内部持有一个 ThreadPoolExecutor。它的默认值很危险:corePoolSize=1、maxPoolSize=Integer.MAX_VALUE、queueCapacity=Integer.MAX_VALUE——不显式配置就等于在用一个无界池。

追问二:为什么不推荐 Executors? 它把"队列容量"和"最大线程数"这两个决定生死的参数藏起来了:newFixedThreadPool 用无界队列,newCachedThreadPool 用无界线程数。详见"为什么禁止使用 Executors"一篇。

追问三:new Thread() 算创建线程池吗? 不算。线程池的核心价值是复用 + 排队 + 拒绝 + 监控,裸线程每次都要走 OS 创建,量大时直接把机器拖垮。

追问四:ScheduledThreadPoolExecutor 有什么坑? 任务里抛出的未捕获异常会终止该任务的后续调度(不会影响其他任务,但周期任务悄悄停了)。所以定时任务体必须整体 try-catch,否则"任务只跑了一次"这种 bug 很难发现。

小结

创建线程池的选择只有一句话:能用构造器就别用工厂,能显式填队列容量就别用默认值。Executors 适合写 demo,ThreadPoolExecutor 适合上生产,虚拟线程执行器适合 IO 密集的新代码。

笔记加载中…