vault backup: 2026-08-09 19:06:40

This commit is contained in:
2026-08-09 19:06:40 +08:00
parent e2975eb86b
commit 9d664545c9
46 changed files with 7287 additions and 0 deletions
@@ -0,0 +1,146 @@
---
tags: [test/review, java, thread-pool, ThreadPoolExecutor, rejection-policy, task-queue]
create time: 2026-08-09 12:00
---
# 线程池参数与拒绝策略_测试题
## 概述
本测试覆盖 ThreadPoolExecutor 七大参数、任务提交流程、四种拒绝策略、工作队列类型以及 CPU/IO 密集型调优公式,共 10 道题目(6 道选择题 + 3 道填空题 + 1 道简答题)。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
ThreadPoolExecutor 构造方法的七个参数中,前五个决定了线程池的什么?
A. 线程的安全性和死锁检测
B. 容量模型和任务调度逻辑
C. 线程的名称格式和优先级
D. 拒绝策略的具体行为
### Q2(基础)— 考察行为判断
调用 `Executors.newFixedThreadPool(10)` 时,底层实际使用的是哪种队列?
A. `ArrayBlockingQueue`(有界)
B. `LinkedBlockingQueue`(无界,capacity = Integer.MAX_VALUE)
C. `SynchronousQueue`
D. `PriorityBlockingQueue`
### Q3(进阶)— 考察核心原理
当一个线程池配置了 `corePoolSize=8`、`maximumPoolSize=16`、`workQueue` 容量为 100 时,假设当前已有 8 个核心线程在运行,此时同时提交 150 个新任务。请问有多少任务会被放入队列、多少会触发非核心线程创建、多少会触发拒绝策略?
A. 100 入队 + 0 创建非核心 + 50 触发拒绝
B. 100 入队 + 8 创建非核心 + 42 触发拒绝
C. 100 入队 + 16 创建非核心 + 34 触发拒绝
D. 150 全部入队,不会触发非核心线程
### Q4(进阶)— 考察比较与辨析
关于四种拒绝策略,以下描述**正确**的是:
A. `AbortPolicy` 是默认策略,会静默丢弃任务而不抛出异常
B. `CallerRunsPolicy` 会将任务回退到提交任务的线程执行,起到降速缓冲的作用
C. `DiscardOldestPolicy` 是最安全的策略,永远不会丢失任何任务
D. `DiscardPolicy` 适合处理可以丢失的非关键任务,会抛出异常通知调用方
### Q5(深入)— 考察场景推理
某服务在流量突增时出现了 OOM 错误,经排查发现使用的是 `Executors.newCachedThreadPool()`。最可能的原因是:
A. CachedThreadPool 的工作队列是有界的,流量大会导致拒绝策略触发
B. CachedThreadPool 的核心线程数为 0,所有任务都必须创建新线程,无限增长导致 OOM
C. CachedThreadPool 的 keepAliveTime 太短,频繁创建销毁线程带来额外开销
D. CachedThreadPool 使用 ArrayBlockingQueue 且有容量上限
### Q6(深入)— 考察数值计算
在一台 8 核机器上运行 IO 密集型任务(阻塞系数取 0.85),根据经验公式,推荐的线程数约为:
A. 9 个(N + 1)
B. 16 个(2 × N)
C. 53 个(N / (1 - 0.85))
D. 128 个(N × 16)
---
## 二、填空题(3道)
### F1 — 填空1
线程池任务提交的流转逻辑遵循以下优先级:第一步创建核心线程;第二步如果已达核心线程数且队列未满则__(1)__;第三步如果队列已满但线程数未达到 maximumPoolSize 则创建__(2)__;第四步如果线程数达到上限且队列仍满则触发__(3)__。
> **提示**: 回忆流程图中的四个分支路径,注意每一步的前置条件。
### F2 — 填空2
在 8 核机器上处理 CPU 密集型任务,推荐的线程数公式是 __(1)__(其中 N 为 CPU 核心数),多出来的那一个线程是为了容忍页缺失等偶发停顿。而对于 IO 密集型任务(阻塞系数 0.85),公式则是 __(2)__。
> **提示**: CPU 密集型和 IO 密型的公式完全不同,前者简单后者需要引入阻塞系数。
### F3 — 填空3
`ArrayBlockingQueue` 属于__(1)__类型的阻塞队列,特点是 FIFO 且可控,__(2)__在生产中使用。与之相对,`LinkedBlockingQueue` 的默认 capacity 为 __(3)__,容易导致任务堆积甚至 OOM。
> **提示**: 关注各队列类型的"有界/无界"属性和生产推荐度。
---
## 三、简答题(1道)
### S1
某微服务使用了如下线程池配置进行异步任务处理:
```java
ExecutorService pool = Executors.newFixedThreadPool(20);
for (Request req : batchRequests) {
pool.submit(() -> process(req)); // 每个请求耗时约 200ms
}
```
大促期间突然出现以下现象:
- 服务响应变慢,RT 飙升
- 监控显示 JVM 堆内存持续上升并最终 OOM
- CPU 使用率正常,说明不是计算瓶颈
请分析:
1. 这段代码存在什么根本性问题?为什么会触发 OOM?
2. 如果要改造这段代码,至少需要改进哪三个方面?(结合线程池配置原则)
3. 如果决定将拒绝策略改为 `CallerRunsPolicy`,需要注意什么风险?
> **答题框架提示**:
> - 第 1 问抓住 Executors.newFixedThreadPool 的底层队列特性
> - 第 2 问从"有界队列 + 自定义核心线程数 + 手动构造"入手
> - 第 3 问从 CallerRunsPolicy 的行为特征推导其对业务链路的影响
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | B | 前五个参数(corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue)共同定义了线程池的容量模型和任务调度逻辑。threadFactory 和 handler 是扩展点。 |
| Q2 | B | `newFixedThreadPool` 内部使用 `LinkedBlockingQueue`,其默认 capacity 为 `Integer.MAX_VALUE`(无界),这会导致任务不断堆积而不会触发非核心线程创建或拒绝策略。 |
| Q3 | B | 核心线程 8 个已满,150 个任务先到队列排,队列容量 100 入队 100 个;剩余 50 个触发第三步创建非核心线程,最多可创建 16-8=8 个非核心线程处理 8 个任务;最后 50-8=42 个触发拒绝策略。 |
| Q4 | B | B 正确:CallerRunsPolicy 由提交线程执行,起到降速缓冲作用。A 错(AbortPolicy 抛出异常),C 错(DiscardOldestPolicy 会丢弃老任务),D 错(DiscardPolicy 静默丢弃不抛异常)。 |
| Q5 | B | CachedThreadPool 的 corePoolSize=0,所有任务都需新建线程处理,且 keepAliveTime=60s。无界队列 + 无限创建线程,在高并发下线程数暴增导致 OOM。 |
| Q6 | C | IO 密集型公式:N / (1 - 阻塞系数) = 8 / (1 - 0.85) = 8 / 0.15 ≈ 53 个线程。CPU 密集型的 N+1=9 不适用于 IO 场景。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | (1)放入队列排队 (2)非核心线程 (3)拒绝策略 | 任务提交的四级决策路径:核心线程→队列→非核心线程→拒绝策略。缺一不可。 |
| F2 | (1)N + 1 (2)N / (1 - 阻塞系数) | CPU 密集型几乎一直在计算,不需要等待 IO,多余线程只会增加上下文切换开销;IO 密集型线程大量时间在等待 IO,可以多开线程提高利用率。 |
| F3 | (1)有界 (2)推荐 (3)Integer.MAX_VALUE | ArrayBlockingQueue 明确指定容量上限,安全可控;LinkedBlockingQueue 默认无界是大坑。 |
### 简答题参考答案
S1:**参考答案要点**:
1. 根本问题:`Executors.newFixedThreadPool(20)` 底层使用无界队列 `LinkedBlockingQueue`(capacity = MAX_VALUE),150 个任务全部入队排队,永远不会创建超过 20 个线程。但由于 `process(req)` 耗时 200ms,队列中堆积的任务引用不会被释放,加上请求对象本身的内存占用,堆持续增长最终 OOM。本质上是一个典型的"无界队列导致的任务堆积 OOM"。
2. ① 换为有界队列的 `ThreadPoolExecutor`(如 `ArrayBlockingQueue(100)`);② 根据 IO 密集型公式重新计算核心线程数和最大线程数;③ 显式配置拒绝策略(如 `CallerRunsPolicy`)而非依赖默认的 AbortPolicy 抛异常打断链路。
3. `CallerRunsPolicy` 会将任务回退到提交线程执行——在这个场景下,如果提交线程是 HTTP 请求线程,那么处理本身就需要 200ms 的任务会在请求线程里同步执行,直接拖慢整个业务链路,导致更多的请求涌入时雪崩效应加剧。使用时务必确认调用方的线程性质。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。
## 关联笔记
- [[01.Java/concurrent/锁升级与 CAS 机制]]
@@ -0,0 +1,137 @@
---
tags: [test/review, java, lock-escalation, CAS, AQS, optimistic-lock]
create time: 2026-08-09 12:00
---
# 锁升级与 CAS 机制_测试题
## 概述
本测试覆盖 synchronized 锁升级机制(偏向→轻量级→重量级)、CAS 原子操作原理、ABA 问题及其解决方案、AQS 框架设计等核心内容,共 10 道题目(6 道选择题 + 3 道填空题 + 1 道简答题)。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
synchronized 锁升级的正确顺序是:
A. 重量级 → 轻量级 → 偏向锁
B. 偏向锁 → 轻量级 → 重量级
C. 轻量级 → 偏向锁 → 重量级
D. 偏向锁 → 重量级 → 轻量级
### Q2(基础)— 考察概念记忆
关于偏向锁,以下说法**正确**的是:
A. JDK 17 中仍然默认启用,通过 `-XX:+UseBiasedLocking` 控制
B. 偏向锁撤销时需要全局 STW,代价较高,因此在真实场景中很少能发挥优势
C. 偏向锁状态下,同一线程重复获取锁也需要进行一次 CAS 操作
D. 当有其他线程尝试获取偏向锁时,当前锁会直接升级为重量级锁
### Q3(进阶)— 考察核心原理
在轻量级锁获取过程中,线程首先在栈帧中创建 Lock Record,然后使用 CAS 将对象头的 Mark Word 替换为指向 Lock Record 的指针。如果 CAS 失败,接下来会发生什么?
A. 立即升级为重量级锁
B. 线程进入睡眠状态等待其他线程释放锁
C. 当前线程自旋重试(最多循环指定次数)
D. 抛出 ConcurrencyModificationException 异常
### Q4(进阶)— 考察比较与辨析
`synchronized` 关键字与 `ReentrantLock` 的主要区别,以下描述**错误**的是:
A. synchronized 是 JVM 内置关键字,ReentrantLock 是 JDK API 层的实现
B. synchronized 支持锁升级(偏向→轻量级→重量级),ReentrantLock 没有锁升级机制
C. synchronized 支持超时获取锁(tryLock 语法),ReentrantLock 不支持
D. ReentrantLock 可选公平锁和非公平锁,synchronized 始终是非公平的
### Q5(深入)— 考察场景推理
在某高并发计数器场景中,开发者使用 `AtomicInteger` 代替 `synchronized` 来提升性能。但在实际压测中发现,随着并发量的增加,CPU 使用率反而急剧升高,QPS 并未如预期增长。最可能的原因是:
A. AtomicInteger 本身存在线程安全问题,在高并发下会漏计
B. CAS 长时间自旋增加了 CPU 开销,竞争激烈时应升级为重量级锁或使用其他方案
C. AtomicInteger 的 value 字段缺少 volatile 修饰,线程间看不到最新值
D. AtomicInteger 的 incrementAndGet() 不是原子操作
### Q6(深入)— 考察源码级理解
AQS 的 CLH 队列中,当一个节点的状态为 `SIGNAL` 时,意味着什么?
A. 该线程已取消等待(CANCELLED)
B. 该线程的后继节点需要被 unpark(即前驱释放锁后会唤醒后继)
C. 该线程正在等待条件变量(CONDITION)
D. 该节点处于共享模式传播状态(PROPAGATE)
---
## 二、填空题(3道)
### F1 — 填空1
在轻量级锁的获取流程中,竞争发生时线程会进行自旋重试。如果自旋超过阈值(默认 __(1)__ 次,可通过 `-XX:PreBlockSpin` 调整),就会升级为__(2)__锁。此时未获得锁的线程会被__(3)__挂起,进入 OS 的等待队列,需要切换到内核态,这是性能损耗最大的环节。
> **提示**: 回忆轻量级锁的自旋阈值和重量级锁的关键特征。
### F2 — 填空2
CAS 是 CPU 级别的原语指令,在 x86 架构下对应__(1)__汇编指令。在多核环境下通过总线锁或缓存锁保证原子性。CAS 的一个经典问题是__(2)__问题——线程 T1 读取值为 A,T2 把值改为 B 再改回 A,T1 的 CAS 检查时发现值仍是 A 但实际值已经被修改过。解决思路是给值加__(3)__,每次变更版本号加 1。
> **提示**: x86 的 cmpxchg 指令是 CAS 的硬件基础,ABA 的解法是加版本戳。
### F3 — 填空3
AQS 的核心是一个 `volatile int state` 变量。在不同同步器中,state 的含义不同:ReentrantLock 中 state 表示__(1)__;CountDownLatch 中 state 表示__(2)__(递减到 0 后触发);Semaphore 中 state 表示__(3)__。
> **提示**: state 的含义取决于具体实现类的语义,回忆文中 AQS 状态机表格。
---
## 三、简答题(1道)
### S1
某电商秒杀系统在订单扣减环节面临高并发竞争。开发团队最初使用 `synchronized` 保护库存扣减逻辑,后发现性能瓶颈。团队计划迁移到 `ReentrantLock` + AQS 的方案。已知以下条件:
- 峰值 QPS 约 5000
- 库存扣减需要保证原子性和一致性
- 部分上游服务依赖超时较短(200ms),需要支持锁的超时获取
请回答:
1. 从 synchronized 迁移到 ReentrantLock 的主要优势有哪些?列出至少 3 个。
2. 如果采用 ReentrantLock 的非公平锁模式,请简述其 tryAcquire 的核心逻辑流程(涉及 CAS 和重入)。
3. 在 AQS 的 CLH 队列中,如果一个节点的线程已经超时,AQS 如何处理这个节点?节点可能的状态有哪些?
> **答题框架提示**:
> - 第 1 问从公平性、中断、超时、Condition 等维度对比
> - 第 2 问结合 ReentrantLock tryAcquire 的代码逻辑
> - 第 3 问列举 AQS 节点状态及其含义
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | B | synchronized 的锁升级路径是固定的:无锁→偏向锁→轻量级锁→重量级锁。一旦升级到重量级就不会再降级(除非竞争减弱时从退出队列恢复)。 |
| Q2 | B | 偏向锁在实际场景中"永远只有一个人访问"的概率很低,而撤销代价高(需全局 STW),因此 JDK 15 标记废弃、JDK 17 移除。A 错在版本不对,C 错在偏向锁重复获取无需 CAS,D 错在是升级为轻量级而非直接重量级。 |
| Q3 | C | CAS 失败后线程会自旋重试,最多循环指定次数(默认 10 次)。超过阈值才升级为重量级锁。不会立即升或抛异常。 |
| Q4 | C | C 是错误的描述:synchronized 不支持超时获取锁,ReentrantLock 才有 `tryLock(timeout)`。A、B、D 都是正确的。 |
| Q5 | B | 在高并发竞争下,CAS 自旋重试会消耗大量 CPU,这就是为什么有界锁最终会升级为重量级锁。QPS 不上涨是因为 CPU 变成了瓶颈。 |
| Q6 | B | SIGNAL 状态表示该节点的后继节点需要被 unpark——即前驱释放锁后会唤醒后继。CANCELLED=已取消,CONDITION=等待条件变量,PROPAGATE=共享模式传播。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | (1)10 (2)重量级 (3)阻塞 | 轻量级锁自旋阈值为 10 次,超时会退化为重量级锁。重量级锁通过 Monitor 实现,线程阻塞挂起需要内核态切换。 |
| F2 | (1)cmpxchg (2)ABA (3)版本号 | cmpxchg 是 x86 的 CAS 汇编指令。ABA 问题的解法是 AtomicStampedReference ——给值加版本号。 |
| F3 | (1)持有锁的次数(重入计数) (2)计数器初始值 (3)可用许可证数量 | state 的含义因同步器而异,但其声明始终是 volatile int,保证可见性。 |
### 简答题参考答案
S1:**参考答案要点**:
1. 主要优势:① 支持公平锁/非公平锁可选,可以根据业务选择合适的策略;② 支持 `lockInterruptibly()` 响应中断,避免因某个锁永远拿不到而导致线程无限阻塞;③ 支持 `tryLock(timeout)` 超时获取,满足上游 200ms 超时约束;④ 支持多个 Condition 变量,比 synchronized 的 wait()/notify() 更灵活。
2. 非公平锁 tryAcquire 核心流程:① 先尝试直接用 CAS 拿锁(`compareAndSetState(0, acquires)`),成功则设置独占线程;② 如果 state 不为 0,判断当前线程是否为已有的独占线程,如果是则重入累加 state;③ 都不满足则返回 false,走 AQS 排队流程。
3. AQS 节点可能的状态包括:`CANCELLED`(值为 1,已取消等待)、`SIGNAL`(值为 -1,后继需 unpark)、`CONDITION`(值为 -2,等待条件变量)、`PROPAGATE`(值为 -3,共享模式传播)。如果节点超时,通常会将其状态设为 CANCELLED 并从队列中移除(由前驱节点的 unlinkCancelledNodes 或 self-unpark 机制处理)。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。
## 关联笔记
- [[01.Java/concurrent/线程池参数与拒绝策略]]