Files
autumn-recruitment/01.Java/concurrent/线程池参数与拒绝策略_test.md

147 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 机制]]