线程池核心参数
ThreadPoolExecutor 最核心的是七个构造参数。线程池表现出来的所有行为,基本都能从这些参数推导出来。
构造方法
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)参数总览
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 时,默认不会因为空闲超时被回收。
flowchart TD
A["任务提交"] --> B{"当前线程数小于 corePoolSize"}
B -- "是" --> C["优先创建工作线程执行任务"]
B -- "否" --> D["尝试进入队列"]如果 corePoolSize 太小,任务很快进入队列,请求延迟上升。如果太大,线程长期占用内存和调度资源,还可能让下游数据库、Redis、HTTP 接口承受过大压力。
maximumPoolSize
maximumPoolSize 表示线程池最多能创建多少工作线程。只有当核心线程已满、队列也放不下任务时,线程池才会尝试创建超过核心数的临时线程。
flowchart TD
A["队列已满"] --> B{"当前线程数小于 maximumPoolSize"}
B -- "是" --> C["创建非核心工作线程"]
B -- "否" --> D["触发拒绝策略"]为什么不一开始就创建到最大线程数:线程不是越多越好。线程越多,上下文切换、线程栈内存、锁竞争和下游压力越大。线程池的目标是稳定处理任务,不是盲目增加并发。
keepAliveTime
keepAliveTime 控制空闲线程的回收时间。默认主要影响超过 corePoolSize 的线程。如果调用 allowCoreThreadTimeOut(true),核心线程也可以超时回收。
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
线程工厂最重要的作用是给线程命名。
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
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 个任务,满了以后由提交线程自己执行,降低提交速度。
面试标准回答
问题:线程池有哪些核心参数?
标准回答:
线程池核心参数包括核心线程数、最大线程数、空闲线程存活时间、时间单位、阻塞队列、线程工厂和拒绝策略。核心线程数控制常驻处理能力,阻塞队列用于缓冲任务,最大线程数用于队列满时临时扩容,拒绝策略用于线程和队列都满时保护系统。生产环境要使用有界队列、自定义线程名,并根据业务类型和下游容量配置参数。
本章小结
线程池参数不能孤立理解。corePoolSize、workQueue、maximumPoolSize 和 handler 决定任务流转;threadFactory 决定排查体验;keepAliveTime 决定资源回收。配置线程池前,必须先知道任务类型、峰值流量、下游容量和失败处理策略。
