面试必备:线程池核心参数与工作原理
为什么需要线程池?
不用线程池的问题
// 每来一个任务创建一个线程
public void badApproach(Runnable task) {
new Thread(task).start(); // 问题:无限创建线程,耗尽资源
}
使用线程池的好处
不用线程池: 用线程池:
用户请求 ──→ 新建线程A 用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
用户请求 ──→ 新建线程B 用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
用户请求 ──→ 新建线程C 用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
...无限制... ...复用固定数量...
优势:
- 复用线程,减少开销
- 控制并发数量,防止资源耗尽
- 统一管理,任务队列缓冲
七大核心参数
public ThreadPoolExecutor(
int corePoolSize, // ① 核心线程数
int maximumPoolSize, // ② 最大线程数
long keepAliveTime, // ③ 空闲线程存活时间
TimeUnit unit, // ④ 时间单位
BlockingQueue<Runnable> workQueue, // ⑤ 任务队列
ThreadFactory threadFactory, // ⑥ 线程工厂
RejectedExecutionHandler handler // ⑦ 拒绝策略
) { ... }
图解线程池工作原理
┌─────────────────────────────────────────┐
│ 线程池 │
│ │
新任务到来 ────────┼─────────┐ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 线程数 < core? │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 是 │ 否 │
│ ▼ ▼ │
│ ┌────────┐ ┌──────────────┐ │
│ │创建核心 │ │ 队列满? │ │
│ │线程执行 │ └──────┬───────┘ │
│ └────────┘ │ │
│ ┌──────┴──────┐ │
│ │ 是 │ 否 │
│ ▼ ▼ │
│ ┌────────────┐ ┌─────────────┐ │
│ │ 线程数 < │ │ 拒绝策略 │ │
│ │ max? │ │ │ │
│ └─────┬──────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │创建临时线程│ │
│ │执行任务 │ │
│ └────────────┘ │
│ │
│ keepAliveTime 后释放临时线程 │
│ │
└─────────────────────────────────────────┘
参数详解
| 参数 | 说明 | 示例 |
|---|---|---|
| corePoolSize | 核心线程数,一直存在 | 5 |
| maximumPoolSize | 最大线程数 | 10 |
| keepAliveTime | 空闲线程存活时间 | 60 |
| unit | 时间单位 | TimeUnit.SECONDS |
| workQueue | 任务队列 | new LinkedBlockingQueue(100) |
| threadFactory | 线程工厂(可自定义线程名) | Executors.defaultThreadFactory() |
| handler | 拒绝策略 | new ThreadPoolExecutor.AbortPolicy() |
拒绝策略
当线程池和队列都满了,无法处理新任务时的处理方式。
四种内置策略
// ① 抛出异常(默认)
new ThreadPoolExecutor.AbortPolicy()
// 抛出 RejectedExecutionException
// ② 由调用线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
// 让提交任务的线程自己执行
// ③ 丢弃任务,不抛异常
new ThreadPoolExecutor.DiscardPolicy()
// 静默丢弃,不通知
// ④ 丢弃最老的任务
new ThreadPoolExecutor.DiscardOldestPolicy()
// 丢弃队列中最老的任务,然后重试
自定义拒绝策略
// 实现 RejectedExecutionHandler
RejectedExecutionHandler handler = (r, executor) -> {
// 自定义处理:记录日志、写入文件、发告警
log.error("线程池满了,拒绝任务: {}", r);
throw new RejectedExecutionException("线程池已满");
};
常见线程池类型
1. FixedThreadPool(固定大小)
// 核心=最大,队列无界
ExecutorService pool = Executors.newFixedThreadPool(10);
// 内部实现
new ThreadPoolExecutor(
10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
);
特点:线程数固定,无临时线程
问题:队列无限大,可能 OOM
2. CachedThreadPool(缓存线程池)
// 核心=0,最大=Integer.MAX,队列SynchronousQueue
ExecutorService pool = Executors.newCachedThreadPool();
// 内部实现
new ThreadPoolExecutor(
0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()
);
特点:
- 有新任务就创建临时线程
- 60 秒没任务自动回收
- 适合短任务场景
问题:线程数无限,可能 OOM
3. SingleThreadExecutor(单线程池)
// 核心=最大=1,队列无界
ExecutorService pool = Executors.newSingleThreadExecutor();
// 内部实现
new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
);
特点:只有一个线程,任务串行执行
4. ScheduledThreadPool(定时线程池)
// 定时任务
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
// 延迟 3 秒执行
pool.schedule(() -> System.out.println("延迟任务"), 3, TimeUnit.SECONDS);
// 延迟 1 秒,每 2 秒执行一次
pool.scheduleAtFixedRate(() -> System.out.println("周期任务"), 1, 2, TimeUnit.SECONDS);
为什么不推荐 Executors 创建线程池?
// 阿里巴巴规范不允许
ExecutorService pool = Executors.newCachedThreadPool();
ExecutorService pool = Executors.newFixedThreadPool(10);
// 推荐手动创建
new ThreadPoolExecutor(
corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue,
threadFactory, handler
);
原因:
- FixedThreadPool/CachedThreadPool 用无界队列,可能 OOM
- CachedThreadPool 线程数无上限,可能 OOM
最佳实践
如何设置线程数?
// CPU 密集型:core = CPU核数 + 1
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool = new ThreadPoolExecutor(
cores + 1, cores + 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(100)
);
// IO 密集型:core = CPU核数 * 2(或根据 IO 耗时比例计算)
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool = new ThreadPoolExecutor(
cores * 2, cores * 2, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
// 公式:线程数 = CPU核数 * (1 + IO耗时/CPU耗时)
完整示例
public class ThreadPoolConfig {
public static ExecutorService createPool() {
int cores = Runtime.getRuntime().availableProcessors();
return new ThreadPoolExecutor(
cores, // corePoolSize
cores * 2, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<>(200), // 队列有界,防止 OOM
new ThreadFactory() { // 自定义线程名
private final AtomicInteger i = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "pool-thread-" + i.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者执行
);
}
}
面试高频问题
Q1:execute 和 submit 的区别?
// execute:提交 Runnable,无返回值
pool.execute(() -> System.out.println("任务"));
// submit:提交 Runnable/Callable,有返回值
Future<String> future = pool.submit(() -> "结果");
String result = future.get(); // 阻塞获取结果
Q2:线程池的拒绝策略在什么时机触发?
- 线程池调用了
shutdown() - 线程池达到饱和(队列满 + 线程数达到最大)
Q3:keepAliveTime 的作用?
当线程数 > corePoolSize 时,空闲线程最多存活 keepAliveTime 时间后被回收。
// 核心线程 5,最大 10,空闲 60 秒后回收临时线程
new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, ...);
Q4:synchronized 和线程池的区别?
| 维度 | synchronized | 线程池 |
|---|---|---|
| 作用 | 同步一段代码 | 管理多个任务 |
| 线程 | 每次 new | 复用已有线程 |
| 目的 | 保证原子性、可见性 | 控制并发数量 |
| 关系 | 可以配合使用 | 独立使用 |
总结
线程池参数:
├── corePoolSize:核心线程数
├── maximumPoolSize:最大线程数
├── keepAliveTime:临时线程存活时间
├── workQueue:任务队列
├── threadFactory:线程工厂
└── handler:拒绝策略
工作流程:
新任务 → 核心线程执行 → 队列缓冲 → 临时线程执行 → 拒绝策略
拒绝策略:
├── AbortPolicy:抛异常
├── CallerRunsPolicy:调用者执行
├── DiscardPolicy:丢弃
└── DiscardOldestPolicy:丢弃最老