线程池
线程池是管理线程的容器,通过复用线程减少创建销毁开销,提高系统性能。
为什么使用线程池?
- 降低资源消耗:复用线程,减少创建销毁开销
- 提高响应速度:任务到达时无需等待线程创建
- 便于管理:统一控制线程数量、监控状态
ThreadPoolExecutor 核心参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)参数详解
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数,即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut) |
| maximumPoolSize | 最大线程数,当队列满时创建临时线程 |
| keepAliveTime | 临时线程空闲存活时间 |
| workQueue | 等待执行的任务队列 |
| threadFactory | 线程工厂,可自定义线程名称、优先级等 |
| handler | 拒绝策略,队列满且线程数达到最大时触发 |
任务执行流程
提交任务
↓
核心线程数未满? → 创建核心线程执行任务
↓ 是
加入队列
↓
队列未满? → 加入队列等待
↓ 是
线程数未达最大? → 创建临时线程执行任务
↓ 是
执行拒绝策略四大拒绝策略
| 策略 | 类名 | 行为 |
|---|---|---|
| AbortPolicy | 默认策略 | 抛出 RejectedExecutionException |
| CallerRunsPolicy | 调用者运行 | 由提交任务的线程执行 |
| DiscardPolicy | 直接丢弃 | 静默丢弃任务 |
| DiscardOldestPolicy | 丢弃最老 | 丢弃队列最老任务,重新提交 |
// 自定义拒绝策略
executor.setRejectedExecutionHandler((r, executor) -> {
// 记录日志
log.warn("任务被拒绝: {}", r);
// 持久化到数据库,后续重试
});三种队列类型
| 队列类型 | 说明 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界队列 | 资源有限,防止 OOM |
| LinkedBlockingQueue | 无界队列 | 任务量不可预估时 |
| SynchronousQueue | 直接提交 | 高吞吐,每个任务立即执行 |
注意:无界队列可能导致 OOM,推荐使用有界队列。
Executors 工厂方法(不推荐生产使用)
| 方法 | 说明 | 问题 |
|---|---|---|
| newFixedThreadPool | 固定线程数 | 无界队列,可能 OOM |
| newSingleThreadExecutor | 单线程 | 无界队列,可能 OOM |
| newCachedThreadPool | 可缓存线程池 | 最大线程数 Integer.MAX_VALUE |
| newScheduledThreadPool | 定时任务 | 无界队列 |
阿里规约:禁止使用 Executors 创建线程池,应通过 ThreadPoolExecutor 直接创建。
正确的线程池配置
// CPU 密集型:核心线程数 = CPU 核数 + 1
int cpuCount = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor cpuIntensive = new ThreadPoolExecutor(
cpuCount + 1, cpuCount + 1,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// IO 密集型:核心线程数 = CPU 核数 * 2
ThreadPoolExecutor ioIntensive = new ThreadPoolExecutor(
cpuCount * 2, cpuCount * 2,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(500),
new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build(),
new ThreadPoolExecutor.AbortPolicy()
);线程池状态
| 状态 | 说明 |
|---|---|
| RUNNING | 运行中,接受新任务 |
| SHUTDOWN | 不接受新任务,处理完队列任务 |
| STOP | 不接受新任务,中断正在执行的任务 |
| TIDYING | 所有任务终止,执行 terminated() |
| TERMINATED | terminated() 执行完毕 |
executor.shutdown(); // 平滑关闭
executor.shutdownNow(); // 立即关闭
executor.awaitTermination(60, TimeUnit.SECONDS); // 等待终止监控线程池
ThreadPoolExecutor executor = ...;
// 监控指标
int active = executor.getActiveCount(); // 活跃线程数
long completed = executor.getCompletedTaskCount(); // 完成任务数
int queueSize = executor.getQueue().size(); // 队列任务数
int poolSize = executor.getPoolSize(); // 当前线程数
// 自定义监控
executor.setTaskExecutionListener((task, duration) -> {
if (duration > 1000) {
log.warn("任务执行超过 1 秒: {}", task);
}
});线程池异常处理
线程池中的任务异常不会抛出到调用者,需要特殊处理:
// 方式一:任务内部 try-catch
executor.execute(() -> {
try {
// 业务逻辑
} catch (Exception e) {
log.error("任务执行异常", e);
}
});
// 方式二:自定义线程工厂,设置异常处理器
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, e) -> {
log.error("线程 {} 异常", thread.getName(), e);
});
return t;
};常见面试题
Q1: 核心线程数和最大线程数什么时候创建?
- 核心线程:任务提交时,核心线程数未满则创建
- 临时线程:队列满后,线程数未达最大则创建
Q2: 线程池如何保证线程安全?
线程池使用 ReentrantLock 保护状态变更,使用 CAS 更新线程数(通过 AtomicInteger 的 ctl 字段)。
Q3: 为什么不建议使用 Executors?
FixedThreadPool和SingleThreadPool:无界队列可能 OOMCachedThreadPool:最大线程数无限制可能 OOM- 无法自定义拒绝策略、线程名等
Q4: 如何合理配置线程池参数?
- CPU 密集型:核心线程数 = CPU 核数 + 1
- IO 密集型:核心线程数 = CPU 核数 * 2 或更高
- 混合型:根据 IO 等待时间占比调整
小结
- 线程池通过复用线程提高性能
- 7 大参数:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler
- 4 种拒绝策略:Abort、CallerRuns、Discard、DiscardOldest
- 不推荐使用 Executors,应手动创建线程池
- 使用有界队列防止 OOM
- 根据任务类型合理配置线程数