一、线程池核心参数全解析 📊
ThreadPoolExecutor构造函数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数 🎯
int maximumPoolSize, // 最大线程数 📈
long keepAliveTime, // 空闲线程存活时间 ⏰
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列 📦
ThreadFactory threadFactory, // 线程工厂 🏭
RejectedExecutionHandler handler // 拒绝策略 🚫
)
一句话总结:
线程池就像一个外包公司 🏢,核心员工、临时工、任务队列、应对策略,都需要精心设计!
二、七个参数详解 🔍
1️⃣ corePoolSize - 核心线程数
含义: 线程池的基本大小,即使线程空闲也不会销毁。
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程数 = 5
...
);
// 启动后,线程池会保持5个线程常驻
// 即使没有任务,这5个线程也不会被回收
生活比喻:
公司的正式员工 👔,不管有没有项目,工资照发!
特殊设置:
// 允许核心线程超时(默认false)
pool.allowCoreThreadTimeOut(true);
// 设置后,核心线程空闲超过keepAliveTime也会被回收
2️⃣ maximumPoolSize - 最大线程数
含义: 线程池能创建的最多线程数。
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数 = 10
...
);
// 当任务很多时,可以临时扩展到10个线程
// 但不会超过10个
生活比喻:
公司的员工上限(正式员工+临时工),再多也招不了了!
重要: 只有当队列满了,才会创建超过核心线程数的线程!
任务提交流程:
1. 线程数 < corePoolSize → 创建核心线程
2. 线程数 >= corePoolSize → 放入队列
3. 队列满了 + 线程数 < maximumPoolSize → 创建临时线程
4. 线程数 >= maximumPoolSize + 队列满了 → 执行拒绝策略
3️⃣ keepAliveTime + unit - 空闲线程存活时间
含义: 超过核心线程数的临时线程,空闲多久后会被回收。
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程
10, // 最大线程
60, // 存活时间 = 60
TimeUnit.SECONDS, // 单位:秒
...
);
// 当线程数从10降到5时:
// 多出的5个临时线程,空闲60秒后会被回收
生活比喻:
临时工 👷,项目忙时招来,项目结束后闲置60天就辞退!
4️⃣ workQueue - 任务队列
含义: 用于存放等待执行的任务。
常见队列类型
| 队列类型 | 容量 | 特点 | 适用场景 |
|---|---|---|---|
| ArrayBlockingQueue | 有界 | 数组实现,FIFO | 资源有限,需要控制队列大小 |
| LinkedBlockingQueue | 可有界/无界 | 链表实现,FIFO | 任务处理时间短 |
| SynchronousQueue | 0 | 不存储元素,直接交付 | 任务立即执行,不排队 |
| PriorityBlockingQueue | 无界 | 优先级队列 | 有优先级要求 |
| DelayQueue | 无界 | 延迟队列 | 延迟任务 |
示例:
// 1. 有界队列(推荐)
new ArrayBlockingQueue<>(100); // 最多100个任务排队
// 超过100个 → 创建临时线程 → 超过最大线程数 → 拒绝
// 2. 无界队列(危险!)
new LinkedBlockingQueue<>(); // 默认Integer.MAX_VALUE
// 任务无限排队,maximumPoolSize失效!
// 可能导致OOM(内存溢出)💥
// 3. 零容量队列
new SynchronousQueue<>();
// 任务不排队,直接交付给线程
// 适合任务量不大但要求快速响应
生活比喻:
ArrayBlockingQueue:
有限的等候区 🪑,坐满了就要加人干活
LinkedBlockingQueue:
无限的等候区(危险!),可能排队到天荒地老
SynchronousQueue:
没有等候区,来一个客户就安排一个员工
5️⃣ threadFactory - 线程工厂
含义: 用于创建新线程。
// 自定义线程工厂
ThreadFactory factory = new ThreadFactory() {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("MyPool-" + count.incrementAndGet()); // 设置线程名
thread.setDaemon(false); // 设置为用户线程
thread.setPriority(Thread.NORM_PRIORITY); // 设置优先级
return thread;
}
};
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
factory, // 使用自定义工厂
...
);
为什么要自定义?
- 方便调试 - 有意义的线程名(MyPool-1, MyPool-2...)
- 统计监控 - 记录线程创建数量
- 异常处理 - 设置UncaughtExceptionHandler
// Google Guava的线程工厂(推荐)
import com.google.common.util.concurrent.ThreadFactoryBuilder;
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("MyPool-%d") // 线程名格式
.setDaemon(false) // 是否守护线程
.setPriority(Thread.NORM_PRIORITY) // 优先级
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("线程 {} 异常", thread.getName(), throwable);
})
.build();
6️⃣ handler - 拒绝策略
含义: 当线程池和队列都满了,如何处理新任务。
四种内置策略
// 1. AbortPolicy(默认)- 抛异常 🚫
new ThreadPoolExecutor.AbortPolicy();
// 直接抛出RejectedExecutionException
// 优点:问题立即暴露
// 缺点:任务丢失
// 2. CallerRunsPolicy - 调用者执行 🏃
new ThreadPoolExecutor.CallerRunsPolicy();
// 让提交任务的线程自己执行
// 优点:不丢失任务,自然降速
// 缺点:阻塞提交线程
// 3. DiscardPolicy - 直接丢弃 🗑️
new ThreadPoolExecutor.DiscardPolicy();
// 静默丢弃,不抛异常
// 优点:不阻塞
// 缺点:任务丢失,不知道
// 4. DiscardOldestPolicy - 丢弃最老的 ⏳
new ThreadPoolExecutor.DiscardOldestPolicy();
// 丢弃队列头部的任务,再尝试提交
// 优点:给新任务机会
// 缺点:老任务丢失
生活比喻:
餐厅满座了,怎么办?
AbortPolicy:
"对不起,满了!"(扔出异常)
CallerRunsPolicy:
"要不你自己做?"(调用者执行)
DiscardPolicy:
"算了,不招待了"(静默丢弃)
DiscardOldestPolicy:
"把最早来的客人赶走"(丢弃最老的)
自定义拒绝策略
// 生产环境推荐:记录日志+监控告警
RejectedExecutionHandler handler = (r, executor) -> {
// 1. 记录日志
log.error("任务被拒绝:{}, 当前线程池状态:{}/{}",
r, executor.getActiveCount(), executor.getMaximumPoolSize());
// 2. 监控打点
metrics.increment("thread_pool_reject");
// 3. 告警
if (rejectCount.incrementAndGet() > 100) {
alertService.send("线程池告警", "拒绝任务过多!");
}
// 4. 降级处理
// 可以放入Redis队列,或者持久化到DB
fallbackQueue.offer(r);
};
三、核心线程数设置:IO密集 vs CPU密集 🎯
CPU密集型任务
特点:
- 大量计算 🧮
- CPU使用率高
- 很少等待IO
例子:
- 加密解密
- 图像处理
- 数学计算
- 压缩解压
公式:
CPU密集型线程数 = CPU核心数 + 1
或者:
N_threads = N_cpu + 1
为什么+1?
防止某个线程偶尔的内存缺页等待,让CPU利用率达到100%。
代码示例:
// CPU密集型
int cpuCount = Runtime.getRuntime().availableProcessors(); // 获取CPU核心数
int threads = cpuCount + 1;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
threads, // 核心线程数 = CPU核心数+1
threads, // 最大线程数 = 核心线程数(不需要扩展)
0, // 空闲时间 = 0(不需要回收)
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 例如8核CPU:
// corePoolSize = 9
// maximumPoolSize = 9
IO密集型任务
特点:
- 大量等待 ⏳
- CPU使用率低
- 频繁IO操作(网络、磁盘、数据库)
例子:
- HTTP请求
- 数据库查询
- 文件读写
- RPC调用
公式:
IO密集型线程数 = CPU核心数 × (1 + IO耗时 / CPU耗时)
或者:
N_threads = N_cpu × (1 + W/C)
其中:
- W = 等待时间(Wait time)
- C = 计算时间(Compute time)
例子计算:
场景:HTTP接口调用
- CPU计算时间:10ms
- IO等待时间:90ms (网络请求)
- CPU核心数:8
线程数 = 8 × (1 + 90/10)
= 8 × 10
= 80
经验值:
IO密集型线程数 = CPU核心数 × 2 到 CPU核心数 × 4
保守设置:
corePoolSize = CPU核心数 × 2 (如8核 → 16线程)
激进设置:
corePoolSize = CPU核心数 × 4 (如8核 → 32线程)
代码示例:
// IO密集型
int cpuCount = Runtime.getRuntime().availableProcessors();
int threads = cpuCount * 2; // 或 * 4,根据IO比例调整
ThreadPoolExecutor pool = new ThreadPoolExecutor(
threads, // 核心线程数 = CPU核心数×2
threads * 2, // 最大线程数 = 核心线程数×2(应对突发)
60, // 空闲60秒回收
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // 较大的队列
new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 例如8核CPU:
// corePoolSize = 16
// maximumPoolSize = 32
对比总结表
| 类型 | CPU密集 | IO密集 |
|---|---|---|
| 特征 | 计算多,等待少 | 计算少,等待多 |
| CPU使用率 | 接近100% | 很低 |
| 核心线程数 | CPU核心数+1 | CPU核心数×(1+W/C) |
| 经验值 | 8核→9线程 | 8核→16~32线程 |
| 最大线程数 | 等于核心线程数 | 核心线程数×2 |
| 队列大小 | 适中(100-500) | 较大(1000+) |
| 例子 | 加密、压缩、计算 | HTTP、DB、文件IO |
四、实战案例分析 🔬
案例1:Web服务器线程池
// 场景:处理HTTP请求(IO密集)
// - 数据库查询:50ms
// - Redis查询:5ms
// - 业务计算:5ms
// - 总耗时:60ms,其中IO占55ms
// 服务器:8核CPU
// 计算:
// W/C = 55ms / 5ms = 11
// 线程数 = 8 × (1 + 11) = 96
// 实际设置(考虑资源限制):
ThreadPoolExecutor webPool = new ThreadPoolExecutor(
32, // 核心线程数(8×4,稍微保守)
64, // 最大线程数(应对高峰)
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2000), // 2000个任务排队
new ThreadFactoryBuilder()
.setNameFormat("web-pool-%d")
.build(),
(r, executor) -> {
// 拒绝策略:记录日志+降级
log.error("线程池满,拒绝任务");
metrics.increment("web_pool_reject");
throw new RejectedExecutionException("系统繁忙,请稍后重试");
}
);
案例2:定时任务线程池
// 场景:定时任务(CPU密集,计算报表)
// 8核CPU
ScheduledThreadPoolExecutor taskPool = new ScheduledThreadPoolExecutor(
9, // CPU密集:核心数+1
new ThreadFactoryBuilder()
.setNameFormat("task-pool-%d")
.setDaemon(false) // 非守护线程,确保任务完成
.build(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝新任务
);
// 设置:
taskPool.setRemoveOnCancelPolicy(true); // 取消任务时移除
taskPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); // 关闭时不执行
案例3:文件处理线程池
// 场景:批量处理文件(IO密集)
// - 读取文件:100ms
// - 解析处理:20ms
// - 写入数据库:50ms
// - 总耗时:170ms,其中IO占150ms
// W/C = 150/20 = 7.5
// 线程数 = 8 × (1 + 7.5) = 68
ThreadPoolExecutor filePool = new ThreadPoolExecutor(
32, // 核心线程数
64, // 最大线程数
120, TimeUnit.SECONDS, // 文件处理可能较长,空闲时间设长一点
new ArrayBlockingQueue<>(500),
new ThreadFactoryBuilder()
.setNameFormat("file-pool-%d")
.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 让调用者执行,自然降速
);
案例4:消息消费线程池
// 场景:Kafka消息消费(混合型:IO查询+CPU计算)
// - 消息反序列化:5ms(CPU)
// - 数据库查询:30ms(IO)
// - 业务处理:15ms(CPU)
// - 发送通知:10ms(IO)
// - 总耗时:60ms,CPU=20ms,IO=40ms
// W/C = 40/20 = 2
// 线程数 = 8 × (1 + 2) = 24
ThreadPoolExecutor kafkaPool = new ThreadPoolExecutor(
16, // 核心线程数(稍微保守)
32, // 最大线程数
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5000), // 消息量大,队列设大一点
new ThreadFactoryBuilder()
.setNameFormat("kafka-consumer-%d")
.setUncaughtExceptionHandler((t, e) -> {
log.error("消费线程异常:{}", t.getName(), e);
// 消费失败,可以重新投递
})
.build(),
(r, executor) -> {
// 拒绝策略:暂停消费,等待队列消化
log.warn("线程池满,暂停消费");
kafkaConsumer.pause(); // 暂停消费
}
);
五、动态调整线程池 🔧
方案1:运行时动态调整
// ThreadPoolExecutor支持动态调整
public class DynamicThreadPool {
private ThreadPoolExecutor pool;
// 动态调整核心线程数
public void setCorePoolSize(int size) {
pool.setCorePoolSize(size);
log.info("核心线程数调整为:{}", size);
}
// 动态调整最大线程数
public void setMaximumPoolSize(int size) {
pool.setMaximumPoolSize(size);
log.info("最大线程数调整为:{}", size);
}
// 动态调整队列(不支持,但可以替换线程池)
public void resizeQueue(int capacity) {
// 创建新线程池
ThreadPoolExecutor newPool = new ThreadPoolExecutor(...);
// 优雅关闭老线程池
pool.shutdown();
try {
pool.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
pool.shutdownNow();
}
// 替换
this.pool = newPool;
}
}
方案2:基于监控自动调整
@Component
public class ThreadPoolMonitor {
@Autowired
private ThreadPoolExecutor pool;
@Scheduled(fixedRate = 30000) // 每30秒检查一次
public void monitor() {
int activeCount = pool.getActiveCount(); // 活跃线程数
int poolSize = pool.getPoolSize(); // 当前线程数
int coreSize = pool.getCorePoolSize(); // 核心线程数
int queueSize = pool.getQueue().size(); // 队列大小
// 计算使用率
double usage = (double) activeCount / poolSize;
log.info("线程池状态:活跃{}/总数{},队列{},使用率{:.2f}%",
activeCount, poolSize, queueSize, usage * 100);
// 自动扩容
if (usage > 0.8 && queueSize > 100) {
int newSize = Math.min(coreSize + 5, pool.getMaximumPoolSize());
pool.setCorePoolSize(newSize);
log.info("线程池扩容:{} → {}", coreSize, newSize);
}
// 自动缩容
if (usage < 0.3 && queueSize == 0) {
int newSize = Math.max(coreSize - 5, 5); // 最少保留5个
pool.setCorePoolSize(newSize);
log.info("线程池缩容:{} → {}", coreSize, newSize);
}
// 告警
if (queueSize > 1000) {
alertService.send("线程池告警", "队列积压严重:" + queueSize);
}
}
}
六、常见错误和最佳实践 ⚠️
错误1:使用Executors创建线程池
// ❌ 错误:阿里巴巴规范禁止
ExecutorService pool = Executors.newFixedThreadPool(10);
// 问题:使用LinkedBlockingQueue(无界队列)
// 可能导致OOM
ExecutorService pool = Executors.newCachedThreadPool();
// 问题:maximumPoolSize = Integer.MAX_VALUE
// 可能创建大量线程,导致OOM
// ✅ 正确:手动创建,参数可控
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 20, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // 有界队列
new ThreadFactoryBuilder().setNameFormat("my-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
错误2:线程数设置过大
// ❌ 错误:线程数=1000(太多了!)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1000, 2000, ...
);
// 问题:
// 1. 创建线程开销大(每个线程1MB栈空间)
// 2. 线程切换开销大
// 3. 可能OOM
// ✅ 正确:根据CPU核心数和任务类型计算
int threads = cpuCount * 2; // IO密集型
错误3:队列设置无界
// ❌ 错误
new LinkedBlockingQueue<>(); // 默认Integer.MAX_VALUE
// ✅ 正确
new LinkedBlockingQueue<>(1000); // 明确容量
new ArrayBlockingQueue<>(1000); // 或者用有界队列
最佳实践总结
// ✅ 生产环境推荐配置
ThreadPoolExecutor pool = new ThreadPoolExecutor(
// 1. 核心线程数:根据业务类型计算
calculateCoreSize(),
// 2. 最大线程数:核心线程数×2(应对突发)
calculateCoreSize() * 2,
// 3. 空闲时间:60秒(可调整)
60, TimeUnit.SECONDS,
// 4. 有界队列:容量明确
new ArrayBlockingQueue<>(1000),
// 5. 自定义线程工厂:方便监控
new ThreadFactoryBuilder()
.setNameFormat("biz-pool-%d")
.setDaemon(false)
.setUncaughtExceptionHandler((t, e) ->
log.error("线程异常", e))
.build(),
// 6. 自定义拒绝策略:记录+告警
(r, executor) -> {
log.error("任务拒绝");
metrics.increment("pool_reject");
throw new RejectedExecutionException();
}
);
// 7. 预热线程池
pool.prestartAllCoreThreads();
// 8. 优雅关闭
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
pool.shutdown();
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
}
}));
七、面试应答模板 🎤
面试官:线程池的核心参数如何设置?IO密集型和CPU密集型有什么区别?
你的回答:
线程池有7个核心参数,其中最重要的是corePoolSize和maximumPoolSize。
CPU密集型任务:
- 特点:大量计算,很少等待,CPU使用率高
- 线程数公式:CPU核心数 + 1
- 原因:线程数过多会导致频繁上下文切换,反而降低性能
- 例子:8核CPU → 9个线程
IO密集型任务:
- 特点:大量等待(网络、磁盘、数据库),CPU使用率低
- 线程数公式:CPU核心数 × (1 + IO时间/CPU时间)
- 经验值:CPU核心数 × 2 到 CPU核心数 × 4
- 原因:线程等待时不占用CPU,可以创建更多线程提高吞吐量
- 例子:8核CPU,IO占90% → 32-64个线程
实际设置建议:
- 先根据公式估算
- 压测验证
- 监控调优(观察CPU使用率、响应时间、吞吐量)
- 动态调整
其他参数:
- 队列:有界队列(ArrayBlockingQueue),避免OOM
- 拒绝策略:CallerRunsPolicy(自然降速)或自定义(记录日志+告警)
- keepAliveTime:60秒(可调整)
- threadFactory:自定义(方便监控和调试)
举例:
我之前做过一个订单处理系统,需要调用多个外部服务(IO密集)。8核服务器,每个请求IO占80%。最终设置32个核心线程,64个最大线程,配合1000容量的队列,TPS从500提升到2000。
八、总结 🎯
线程池参数速查表:
参数 CPU密集 IO密集
──────────────────────────────────────────
corePoolSize CPU核心数+1 CPU核心数×2~4
maximumPoolSize 等于core core×2
keepAliveTime 0~60秒 60秒
workQueue 中等(100-500) 较大(1000+)
threadFactory 自定义(日志) 自定义(日志)
handler CallerRuns CallerRuns
记忆口诀:
CPU密集核心加一,
IO密集翻倍起步,
队列有界防爆仓,
拒绝策略要记录,
监控调优是王道!🎵
核心要点:
- ✅ CPU密集:核心数+1
- ✅ IO密集:核心数×(1+W/C)
- ✅ 队列必须有界
- ✅ 自定义线程工厂和拒绝策略
- ✅ 监控+动态调整