线程池调优
问题
如何合理配置线程池参数?线程池队列堆积、任务被拒绝怎么处理?
答案
线程池七大参数
new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
TimeUnit, // 时间单位
workQueue, // 工作队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
参数配置经验
| 场景 | corePoolSize | maximumPoolSize | 队列 |
|---|---|---|---|
| CPU 密集型 | CPU 核数 + 1 | 同 core | 较短队列 |
| IO 密集型 | CPU 核数 × 2 | CPU 核数 × 4 | 中等队列 |
| 混合型 | 按 IO 等待比计算 | core × 2 | 视吞吐而定 |
理论公式
线程数 = CPU 核数 × (1 + IO 等待时间 / CPU 计算时间)
例:8 核 CPU,接口 90% 时间在等 IO(DB/Redis/HTTP)
线程数 = 8 × (1 + 0.9/0.1) = 8 × 10 = 80
不要迷信公式
公式只是起点。生产环境要以压测数据为准——逐步调大线程数,观察吞吐量和 RT 的变化,找到最优点。
队列选择
| 队列 | 特点 | 适用场景 |
|---|---|---|
LinkedBlockingQueue | 无界(默认)/ 有界 | 通用,必须设上限 |
ArrayBlockingQueue | 有界,公平/非公平 | 需要严格控制 |
SynchronousQueue | 不存储,直接交付 | 要求快速响应 |
无界队列的风险
new LinkedBlockingQueue()(无界)→ 任务堆积 → 内存溢出 → OOM。生产环境必须设置有界队列。
拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy | 抛异常(默认) | 需要明确感知过载 |
CallerRunsPolicy | 调用者线程执行 | 不允许丢弃 |
DiscardPolicy | 静默丢弃 | 可丢弃的非核心任务 |
DiscardOldestPolicy | 丢弃队首 | 新的比旧的更重要 |
动态线程池
基于 Nacos 动态调参
@NacosConfigListener(dataId = "thread-pool.yaml")
public void onConfigChange(String config) {
ThreadPoolConfig poolConfig = yaml.loadAs(config, ThreadPoolConfig.class);
// ThreadPoolExecutor 支持运行时修改参数
executor.setCorePoolSize(poolConfig.getCoreSize());
executor.setMaximumPoolSize(poolConfig.getMaxSize());
// 调整队列容量需自定义 ResizableLinkedBlockingQueue
if (executor.getQueue() instanceof ResizableLinkedBlockingQueue) {
((ResizableLinkedBlockingQueue<?>) executor.getQueue())
.setCapacity(poolConfig.getQueueSize());
}
log.info("线程池参数已更新: core={}, max={}, queue={}",
poolConfig.getCoreSize(), poolConfig.getMaxSize(), poolConfig.getQueueSize());
}
线程池监控
核心监控指标
@Scheduled(fixedRate = 10000)
public void monitor() {
ThreadPoolExecutor pool = taskExecutor;
log.info("线程池状态 - 活跃:{}/{}, 队列:{}/{}, 已完成:{}, 已拒绝:{}",
pool.getActiveCount(), // 当前活跃线程
pool.getPoolSize(), // 当前线程总数
pool.getQueue().size(), // 队列中等待的任务
pool.getQueue().remainingCapacity(),
pool.getCompletedTaskCount(), // 已完成任务数
pool.getRejectedExecutionHandler() // 拒绝策略
);
}
告警阈值建议:
- 队列使用率 > 80% → 预警
- 活跃线程数 = 最大线程数 → 预警
- 出现 RejectedExecution → 告警
常见面试问题
Q1: 线程池参数怎么设置?
答案:
没有银弹。先按公式估算(CPU 密集 = N+1、IO 密集 = 2N),再通过压测逐步调整。不同业务场景的参数不同,要根据实际吞吐量和 RT 来确定。
详见 线程池。
Q2: 线程池的工作流程是什么?
答案:
- 提交任务 → 当前线程数 < corePoolSize → 创建核心线程
- 核心线程满 → 加入队列
- 队列满 → 创建非核心线程(不超过 maxPoolSize)
- 都满了 → 触发拒绝策略
Q3: 为什么阿里规约不允许用 Executors?
答案:
Executors.newFixedThreadPool()→LinkedBlockingQueue()无界队列 → OOMExecutors.newCachedThreadPool()→maximumPoolSize = Integer.MAX_VALUE→ 创建过多线程 → OOM- 应该手动
new ThreadPoolExecutor()指定所有参数
Q4: Spring @Async 底层用的什么线程池?
答案:
默认用 SimpleAsyncTaskExecutor(每次 new 一个线程,不复用!),生产环境必须自定义:
@Configuration
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
executor.setThreadNamePrefix("async-");
return executor;
}
}