这是一个符合你所有要求的HTML5静态首页代码。 设计说明: 1. 布局与尺寸:严格遵守PC端1200px主体宽度,满屏Topbar/Nav/Footer。Nav导航悬浮置顶。 2. 视觉风格:主色调 `#FDF0E0` (暖米色),搭配深褐色文字,营造专业、沉稳的攻略类网站氛围。 3. 内容深度:包含10个高频面试题的深度解析、核心概念图解(CSS实现)、技术演进时间轴、以及“网友还关心”的周边知识板块。 4. SEO优化:包含Schema标记、语义化标签、H1-H6层级分明、Meta标签优化。 5. 交互:纯CSS实现的选项卡(Tab)切换功能,无需JavaScript。 6. 特殊要求:关键分词加粗,特殊符号装饰,无多余文字描述。 ```html
专注于Java并发编程技术分享 📧 contact@kaoacca.cn

线程池面试题及答案

深度解析 ThreadPoolExecutor 核心原理、源码分析及实战应用场景

更新时间:2023-10 | 阅读时间:15分钟 | 难度指数:★★★★☆

一、 为什么使用线程池?

在Java并发编程中,线程池(ThreadPool)是最核心的组件之一。面试官常问:“为什么要用线程池?直接 new Thread() 有什么问题?”

⚡ 降低资源消耗

通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

⚡ 提高响应速度

当任务到达时,任务可以不需要等到线程创建就能立即执行。

⚡ 提高线程的可管理性

线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

二、 线程池核心参数详解

掌握 ThreadPoolExecutor 的7个核心参数是回答面试题的基础。以下通过选项卡展示不同维度的解析。

1. corePoolSize (核心线程数)

线程池中的常驻核心线程数。在任务添加到线程池时,如果当前线程数小于 corePoolSize,则创建新线程执行任务。

2. maximumPoolSize (最大线程数)

线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于 maximumPoolSize,则创建新线程来执行任务。

3. keepAliveTime & unit

非核心线程的空闲存活时间。当线程数大于 corePoolSize 时,空闲时间超过 keepAliveTime 的非核心线程会被销毁。

4. workQueue (任务队列)

用于保存等待执行的任务的阻塞队列。常用队列包括:ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue 等。

5. threadFactory (线程工厂)

用于创建新线程。默认工厂创建的线程同属一个组,名字为 "pool-1-thread-1"。建议自定义线程工厂,设置更有意义的名字和异常处理器。

6. handler (拒绝策略)

当队列和最大线程数都满了时的饱和策略。

在 ThreadPoolExecutor 源码中,线程状态和线程数是合并存储在一个 int 变量 workerCount 中的:

// high 3 bits = run state
// low 29 bits = run count
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程数掩码 29位
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// 运行状态掩码
private static final int RUNNING    = -1 << COUNT_BITS;

面试官常问:为什么线程数要占用29位?
答案:因为线程数最多为 2^29 - 1,约5亿个,对于任何单机应用来说都足够了,且节省内存空间。

参数 通俗理解
corePoolSize 正式编制员工,一直在岗。
workQueue 待办事项清单(队列)。
maximumPoolSize 最大员工数(正式+临时)。
keepAliveTime 临时员工空闲多久被辞退。
handler 单子太多做不完时的处理办法(拒绝)。

三、 线程池执行流程与状态

理解线程池的执行流程是回答“任务提交流程”类问题的关键。

步骤 1

提交任务

调用 execute() 或 submit() 提交任务。

步骤 2

创建核心线程

如果当前线程数 < corePoolSize,创建新线程执行任务。

步骤 3

放入队列

如果当前线程数 >= corePoolSize,将任务加入 workQueue。

步骤 4

创建非核心线程

如果队列已满,且当前线程数 < maximumPoolSize,创建非核心线程执行任务。

步骤 5

执行拒绝策略

如果队列已满,且线程数达到 maximumPoolSize,则执行 RejectedExecutionHandler。

线程池的五种状态

四、 拒绝策略详解

当线程池资源耗尽时,如何优雅地处理拒绝任务?这是必考题。

AbortPolicy (默认)

直接抛出 RejectedExecutionException 异常,阻止系统正常运行。

CallerRunsPolicy

由调用线程(提交任务的线程)处理该任务。起到降级和缓速的作用。

DiscardPolicy

直接丢弃任务,没有任何异常抛出。

DiscardOldestPolicy

丢弃队列中最老的任务,尝试为当前任务腾出空间。

面试官追问:如果让你设计一个自定义拒绝策略,你会怎么做?
回答:可以将任务持久化到数据库或消息队列(如RocketMQ/Kafka)中,实现异步削峰填谷,保证数据不丢失。

五、 如何合理配置线程池大小?

这是最具实战意义的问题,没有标准答案,取决于业务类型。

任务类型 特点 配置建议 公式
CPU 密集型 计算密集,少IO 少线程 N + 1 (N为CPU核数)
IO 密集型 大量阻塞等待 多线程 2N 或 N / (1 - 阻塞系数)
混合型 CPU+IO 拆分 拆分为CPU和IO密集型分别处理

注意:以上公式仅为参考,实际生产中需通过压测(Load Testing)确定最佳参数。

六、 高频面试题 Q&A

Q1: 线程池中的 workQueue 队列为什么推荐用 LinkedBlockingQueue?

A: 默认容量为 Integer.MAX_VALUE,容易导致内存溢出(OOM)。在生产环境中,建议指定容量,如 new LinkedBlockingQueue(1000)。

Q2: 如何优雅地关闭线程池?

A: 推荐使用 shutdown() 或 shutdownNow()。

通常配合 awaitTermination() 使用,以确保线程池完全关闭。

Q3: 线程池的 corePoolSize 可以在运行时动态调整吗?

A: 可以。通过 setCorePoolSize() 方法动态设置。但需要注意线程安全,建议在配置中心(如Nacos/Apollo)中管理线程池参数,实现动态刷新。

Q4: 什么是线程池的“浪涌”现象?如何解决?

A: 当大量任务同时提交时,即使队列未满,也可能瞬间创建大量线程(如果maximumPoolSize较大)。
解决: 使用有界队列,并合理设置 maximumPoolSize,或者使用 SynchronousQueue 配合适当的线程工厂。

Q5: ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 有什么区别?

A: ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,主要用于执行定时或周期性任务(scheduleAtFixedRate, scheduleWithFixedDelay)。其内部使用了 DelayQueue。

```
斌知网
蜀ICP备2026017620号-5