5.4 KiB
5.4 KiB
tags, create time, update time
| tags | create time | update time | |||||
|---|---|---|---|---|---|---|---|
|
2026-08-08 18:00 | 2026-08-08 18:00 |
线程池参数与拒绝策略
概述
线程池是 Java 并发编程的核心工具类,通过复用预先创建的线程来降低频繁创建/销毁线程的开销。ThreadPoolExecutor 是所有线程池工厂(Executors)背后的真正实现,直接暴露了全部可调参数——理解它的运行机制是编写可靠并发代码的第一步。
核心原理
ThreadPoolExecutor 七大参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 存活时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
) {}
这七个参数中,前五个决定了线程池的容量模型和任务调度逻辑,后两个属于扩展点。
任务提交流程
当调用 execute(Runnable) 时,任务按以下优先级流转:
flowchart TD
A["提交任务"] --> B{"当前线程数 < corePoolSize?"}
B -->|是| C["创建核心线程执行任务"]
B -->|否| D{"队列是否已满?"}
D -->|否| E["放入工作队列等待"]
D -->|是| F{"当前线程数 < maxPoolSize?"}
F -->|是| G["创建非核心线程执行任务"]
F -->|否| H["执行拒绝策略"]
C --> I[完成]
E --> J{"核心线程<br/>超时检测"}
J -->|未超时| E
J -->|已超时| K["回收线程"]
G --> I
H --> L["记录异常 / 告警"]
流程解读:
- 第一步:如果当前运行线程数小于
corePoolSize,即使有空闲线程也创建新线程执行任务(除非设置了allowCoreThreadTimeOut)。 - 第二步:如果线程数已达核心数且队列未满,将任务放入工作队列排队。
- 第三步:如果队列已满但线程数未达到
maximumPoolSize,创建非核心线程处理。 - 第四步:如果线程数达到上限且队列仍满,触发拒绝策略。
Note
这是面试高频坑点:
Executors.newFixedThreadPool()使用的是无界队列LinkedBlockingQueue(capacity = Integer.MAX_VALUE),导致第 3、4 步永远不会发生——所有任务都在队列中堆积,线程池永远只有 corePoolSize 个线程,maximumPoolSize形同虚设。这在流量突增时会导致 OOM。
四种拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy(默认) |
抛出 RejectedExecutionException |
要求必须处理的场景,快速失败 |
CallerRunsPolicy |
由提交任务的线程直接执行 | 降速缓冲,让提交方承担处理成本 |
DiscardPolicy |
静默丢弃任务 | 可丢失的非关键任务 |
DiscardOldestPolicy |
丢弃队列中最老的任务,再尝试提交 | 保最新数据的批处理场景 |
Warning
CallerRunsPolicy是最"温和"的策略——但它会将任务回退到提交线程(通常是客户请求线程),如果任务耗时较长会阻塞整个业务链路。使用时务必确认调用方的线程性质。
工作队列类型
| 队列 | 类型 | 特点 |
|---|---|---|
ArrayBlockingQueue |
有界数组 | FIFO,公平可控,推荐生产使用 |
LinkedBlockingQueue |
无界链表 | 默认 capacity = MAX_VALUE,易导致堆积 |
SynchronousQueue |
同步传输 | 不存储元素,直接交接,配合 newCachedThreadPool |
PriorityBlockingQueue |
优先队列 | 支持自定义优先级排序 |
// 推荐的线程池配置示例
ExecutorService pool = new ThreadPoolExecutor(
8, // corePoolSize
16, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100), // 有界队列,明确上限
new ThreadFactoryBuilder()
.setNameFormat("biz-pool-%d")
.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 降级而非崩溃
);
CPU 密集型 vs IO 密集型调优公式
线程数的合理设定直接影响吞吐量。经验公式如下:
| 任务类型 | 公式 | 说明 |
|---|---|---|
| CPU 密集型 | N + 1(N = CPU 核心数) | 多一个线程可以容忍页缺失等偶发停顿 |
| IO 密集型 | N / (1 - 阻塞系数) | 阻塞系数通常取 0.8~0.9,即 N × 5 ~ N × 10 |
| 混合型 | 根据各阶段占比加权平均 | CPU 阶段用 N+1,IO 阶段用大倍数 |
举例:在 8 核机器上处理 IO 密集型任务(阻塞系数 0.85):
线程数 ≈ 8 / (1 - 0.85) ≈ 53 个线程
Tip
面试常考点:为什么 CPU 密集型只需 N+1?因为 CPU 密集型的线程几乎一直在计算,不需要等待 IO。多余线程只会增加上下文切换开销,不会提升吞吐。
实践场景
常见反模式排查清单:
- 用
Executors.newFixedThreadPool()接高并发请求 → 换为有界队列的ThreadPoolExecutor - 用
new CachedThreadPool()处理持久化任务 → 缓存池会在空闲 60s 后回收所有线程,反复创建带来额外开销 - 任务没有设置超时机制 → 配合
Future.get(timeout)或CompletableFuture.orTimeout()