Skip to content

线程池核心参数

ThreadPoolExecutor 最核心的是七个构造参数。线程池表现出来的所有行为,基本都能从这些参数推导出来。

构造方法

java
public ThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)

参数总览

mermaid
flowchart TD
    A["ThreadPoolExecutor 参数"] --> B["corePoolSize 核心线程数"]
    A --> C["maximumPoolSize 最大线程数"]
    A --> D["keepAliveTime 空闲存活时间"]
    A --> E["unit 时间单位"]
    A --> F["workQueue 阻塞队列"]
    A --> G["threadFactory 线程工厂"]
    A --> H["handler 拒绝策略"]
参数控制什么如果配置错误
corePoolSize常驻工作线程数量太小吞吐低,太大浪费资源
maximumPoolSize峰值最大线程数量太小容易拒绝,太大拖垮下游
keepAliveTime非核心线程空闲多久回收太短频繁创建销毁,太长浪费线程
unit存活时间单位单位搞错会导致回收异常
workQueue任务排队容器无界队列可能 OOM
threadFactory如何创建线程没有线程名,线上排查困难
handler满载时如何拒绝默认直接抛异常,业务可能没兜底

corePoolSize

corePoolSize 表示线程池希望长期保留的工作线程数量。

误区:核心线程不是每个线程永久贴了一个“核心线程”标签。更准确地说,线程池维护一个保底线程数量。当工作线程数量小于等于 corePoolSize 时,默认不会因为空闲超时被回收。

mermaid
flowchart TD
    A["任务提交"] --> B{"当前线程数小于 corePoolSize"}
    B -- "是" --> C["优先创建工作线程执行任务"]
    B -- "否" --> D["尝试进入队列"]

如果 corePoolSize 太小,任务很快进入队列,请求延迟上升。如果太大,线程长期占用内存和调度资源,还可能让下游数据库、Redis、HTTP 接口承受过大压力。

maximumPoolSize

maximumPoolSize 表示线程池最多能创建多少工作线程。只有当核心线程已满、队列也放不下任务时,线程池才会尝试创建超过核心数的临时线程。

mermaid
flowchart TD
    A["队列已满"] --> B{"当前线程数小于 maximumPoolSize"}
    B -- "是" --> C["创建非核心工作线程"]
    B -- "否" --> D["触发拒绝策略"]

为什么不一开始就创建到最大线程数:线程不是越多越好。线程越多,上下文切换、线程栈内存、锁竞争和下游压力越大。线程池的目标是稳定处理任务,不是盲目增加并发。

keepAliveTime

keepAliveTime 控制空闲线程的回收时间。默认主要影响超过 corePoolSize 的线程。如果调用 allowCoreThreadTimeOut(true),核心线程也可以超时回收。

java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
        4,
        8,
        60,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(100)
);

pool.allowCoreThreadTimeOut(true);

开启核心线程超时适合低频任务,能节省资源;不适合对响应延迟敏感的核心业务,因为线程被回收后下次任务需要重新创建线程。

workQueue

workQueue 决定任务如何排队,也会深刻影响 maximumPoolSize 是否生效。

队列特点对 maximumPoolSize 的影响
ArrayBlockingQueue有界数组队列队列满后才扩到最大线程
LinkedBlockingQueue可有界也可无界无界时最大线程基本不生效
SynchronousQueue不存任务,直接移交容易快速创建到最大线程
PriorityBlockingQueue优先级队列默认无界,要小心堆积

生产环境优先使用有界队列。无界队列不是不限制任务,而是把限制推迟到内存耗尽。

threadFactory

线程工厂最重要的作用是给线程命名。

java
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

class NamedThreadFactory implements ThreadFactory {
    private final AtomicInteger index = new AtomicInteger();
    private final String prefix;

    NamedThreadFactory(String prefix) {
        this.prefix = prefix;
    }

    public Thread newThread(Runnable runnable) {
        Thread thread = new Thread(runnable);
        thread.setName(prefix + "-" + index.incrementAndGet());
        thread.setUncaughtExceptionHandler((t, e) ->
                System.out.println("线程异常:" + t.getName() + " " + e.getMessage()));
        return thread;
    }
}

如果不命名线程,线上 jstack 里只能看到 pool-1-thread-3,很难判断是订单线程池、支付线程池还是采集线程池。

handler

拒绝策略不是错误兜底,而是容量保护机制。线程池满了说明系统处理能力已经达到边界,必须明确选择“失败、反压、丢弃、降级”。

常见策略:

策略行为适合场景
AbortPolicy抛出异常必须明确失败的核心任务
CallerRunsPolicy提交线程自己执行需要反压,允许变慢
DiscardPolicy直接丢弃非核心统计、埋点
DiscardOldestPolicy丢弃队列最老任务很少直接使用,容易误删重要任务

参数组合 Demo

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

public class ThreadPoolParamsDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                8,
                16,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(500),
                new NamedThreadFactory("collect"),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        pool.execute(() -> System.out.println("执行采集任务"));
        pool.shutdown();
    }
}

这个配置表达的是:平时 8 个线程处理采集任务,峰值最多 16 个,队列最多积压 500 个任务,满了以后由提交线程自己执行,降低提交速度。

面试标准回答

问题:线程池有哪些核心参数?

标准回答:

线程池核心参数包括核心线程数、最大线程数、空闲线程存活时间、时间单位、阻塞队列、线程工厂和拒绝策略。核心线程数控制常驻处理能力,阻塞队列用于缓冲任务,最大线程数用于队列满时临时扩容,拒绝策略用于线程和队列都满时保护系统。生产环境要使用有界队列、自定义线程名,并根据业务类型和下游容量配置参数。

本章小结

线程池参数不能孤立理解。corePoolSizeworkQueuemaximumPoolSizehandler 决定任务流转;threadFactory 决定排查体验;keepAliveTime 决定资源回收。配置线程池前,必须先知道任务类型、峰值流量、下游容量和失败处理策略。