Java 线程池常见面试坑点总结
本章节整理一份超实用的《线程池面试坑点》,方便快速记忆,如果在现场讲出来就会显得又专业又高效!
1. 为什么不推荐直接使用 Executors 创建线程池?
答:
因为
Executors工具类(如newFixedThreadPool、newCachedThreadPool)默认使用的队列是无界的,比如LinkedBlockingQueue,这样在高并发情况下容易导致内存耗尽(OOM),从而引发系统崩溃。正确做法是手动使用
ThreadPoolExecutor,并且合理设置核心参数(核心线程数、最大线程数、队列容量、拒绝策略等)。
一句话记忆版:
✅ “Executors 隐患大,ThreadPoolExecutor 可控、安全。”
2. 如何自定义 ThreadPoolExecutor ?
答:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程最大存活时间
TimeUnit.SECONDS,// 时间单位
workQueue, // 阻塞队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
一般推荐:
- 阻塞队列用
ArrayBlockingQueue(有界) - 拒绝策略根据业务选定(比如默认抛异常、CallerRunsPolicy)
3. 常见的线程池拒绝策略有哪些?应用场景是什么?
答:
| 策略 | 说明 | 适用场景示例 |
|---|---|---|
| AbortPolicy(默认) | 直接抛异常,任务被拒绝 | 对实时性要求非常高的系统 |
| CallerRunsPolicy | 提交任务线程自己执行 | 可以降级处理,保护线程池 |
| DiscardPolicy | 直接丢弃新提交的任务 | 可容忍少量任务丢失的场景 |
| DiscardOldestPolicy | 丢弃最老的排队任务,执行新任务 | 保证新请求优先场景 |
一句话记忆版:
✅ “Abort 抛异常,Caller 自己干,Discard 扔掉任务,DiscardOldest 扔最老的。”
4. 如何配置线程池的核心参数?(再强化一遍)
答:
- CPU 密集型 ➔ 核心线程数 = CPU 核数 + 1
- IO 密集型 ➔ 核心线程数 = 2 * CPU 核数(或更多)
- 队列大小 ➔ 根据业务量评估(有界队列!)
- 最大线程数 ➔ 一般是核心线程数的 1.5~2 倍
- 拒绝策略 ➔ 业务特点决定(实时性高就抛异常,可降级就 CallerRuns)
5. 线程池什么时候适合用固定线程池 (FixedThreadPool)?
答:
- 任务数量可控且执行时间较长。
- 系统负载比较稳定。
- 比如后台邮件发送、日志处理等固定任务。
一句话记忆版:
✅ “任务稳定、执行慢,FixedThreadPool 刚刚好。”
6. 什么场景适合用缓存线程池 (CachedThreadPool)?
答:
- 短期大量并发任务、执行时间短。
- 如处理大量短小的异步请求。
风险提醒: CachedThreadPool 无界线程数 ➔ 容易 OOM,要小心用!
7. 线程池的核心线程数可以回收吗?
答:
- 默认核心线程不会回收。
- 如果调用
allowCoreThreadTimeOut(true),即使是核心线程,空闲时间超过keepAliveTime也会被回收。
最后附送一句万能收尾回答(面试压轴)
当面试官问到线程池相关任何问题最后结尾时,可以用下面这句总结,超级加分:
“在高并发系统中,线程池是资源隔离和负载保护的重要手段,合理配置线程池参数,结合限流、熔断、降级等机制,可以有效保障系统稳定性和高可用性。”
更多详细内容请关注其他相关文章!