线程池常见面试题
本章节整理一套线程池常见面试题 ,供大家参考;
1. 为什么线程池要有核心线程(corePoolSize)和最大线程(maximumPoolSize)两个概念?
标准回答:
为了合理控制资源使用和任务处理速度之间的平衡。
- corePoolSize 保证线程池在常态下用固定的少量线程处理日常负载,避免频繁创建销毁线程带来的开销。
- 当瞬时任务量激增时,线程池才根据需要,临时扩展到 maximumPoolSize,快速处理高峰流量。
- 高峰过去后,非核心线程超时回收,回归到节能模式,避免浪费资源。
总结一句话:
“核心线程稳态支撑,最大线程应急扩展。”
2. keepAliveTime 是什么?对核心线程和非核心线程有何不同?
标准回答:
- keepAliveTime:指的是线程空闲超过多少时间后,允许被回收销毁。
- 默认行为:
- 非核心线程:空闲超时后会被销毁。
- 核心线程:默认不会超时销毁。
- 可以设置:通过
allowCoreThreadTimeOut(true),可以让核心线程也超时回收。
小总结:
非核心线程天生能超时,核心线程默认坚持,但可以改设。
3. 什么是阻塞队列?为什么线程池需要用阻塞队列?
标准回答:
- 阻塞队列(BlockingQueue) 是一种线程安全的队列,支持:
- 插入时等待(如果队列满了)。
- 移除时等待(如果队列空了)。
- 在线程池中,阻塞队列的作用是:
- 缓存提交但暂时无可用线程执行的任务。
- 平滑流量高峰,减少直接扩展线程数的频率。
- 协调生产者(提交任务)和消费者(执行任务)的速率不一致问题。
总结一句话:
“阻塞队列是线程池的蓄水池。”
4. 常用的阻塞队列有哪些?不同的适用场景是什么?
标准回答:
常见的 3 种阻塞队列:
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
LinkedBlockingQueue | 无界(或近似无界),基于链表 | 默认首选,普通业务场景,任务量正常可控 |
ArrayBlockingQueue | 有界,基于数组 | 资源受限系统,需要精细控制队列大小 |
SynchronousQueue | 不存储元素,提交必须立刻由线程处理 | 高吞吐、低延迟任务,线程数动态扩展快 |
总结一句话:
“链表型撑大水量,数组型控死容量,同步型压榨极致性能。”
5. 如何优雅地关闭线程池?(而不是直接粗暴 stop)
标准回答:
优雅关闭线程池,标准做法是:
- 调用
shutdown()➔ 拒绝新任务,继续处理已有任务。 - 配合
awaitTermination(timeout, unit)➔ 等待一段时间处理完。 - 如果超时未结束,再调用
shutdownNow()➔ 尝试打断当前执行中的线程。
伪代码示例:
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
总结一句话:
“先温柔 shutdown,再强硬 shutdownNow。”
6. 线程池中的线程死掉了怎么办?(比如线程异常退出)
标准回答:
- ThreadPoolExecutor 内部有容错机制,如果有线程运行异常导致死亡,线程池会自动创建新的线程补充到工作线程池中,保证线程池总量维持在设定范围。
- 可以通过重写
afterExecute(Runnable r, Throwable t)方法,自定义异常监控,比如日志记录。
总结一句话:
“线程死了,池子会自己救场,但建议监控异常。”
7. 如何设计一个防止线程池资源被滥用的机制?
标准回答:
防止滥用资源的做法通常包括:
- 限制
corePoolSize、maximumPoolSize、队列长度。 - 合理选择拒绝策略,比如抛异常报警。
- 对提交到线程池的任务,增加限流控制(如令牌桶、漏桶算法)。
- 对重要任务设置超时时间,避免任务无限执行阻塞线程。
总结一句话:
“控数量 ➔ 拒绝异常 ➔ 提交限流 ➔ 执行限时。”
8. 线程池的饱和策略(拒绝策略)什么时候被触发?
标准回答:
饱和策略(RejectedExecutionHandler)触发时机:
- 当核心线程都在忙;
- 队列满了;
- 最大线程数已满;
- 还有新任务进来时 ➔ 触发拒绝策略。
小口诀记忆:
“忙 + 满 + 满 + 新 ➔ 触发拒绝策略。”
更多详细内容请关注其他详细内容!