--- tags: [java/lang, thread-pool, ThreadPoolExecutor, rejection-policy, task-queue] create time: 2026-08-08 18:00 update time: 2026-08-08 18:00 --- # 线程池参数与拒绝策略 ## 概述 线程池是 Java 并发编程的核心工具类,通过复用预先创建的线程来降低频繁创建/销毁线程的开销。ThreadPoolExecutor 是所有线程池工厂(Executors)背后的真正实现,直接暴露了全部可调参数——理解它的运行机制是编写可靠并发代码的第一步。 ## 核心原理 ### ThreadPoolExecutor 七大参数 ```java public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 非核心线程空闲存活时间 TimeUnit unit, // 存活时间单位 BlockingQueue workQueue, // 工作队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 ) {} ``` 这七个参数中,前五个决定了线程池的**容量模型**和**任务调度逻辑**,后两个属于**扩展点**。 ### 任务提交流程 当调用 `execute(Runnable)` 时,任务按以下优先级流转: ```mermaid flowchart TD A["提交任务"] --> B{"当前线程数 < corePoolSize?"} B -->|是| C["创建核心线程执行任务"] B -->|否| D{"队列是否已满?"} D -->|否| E["放入工作队列等待"] D -->|是| F{"当前线程数 < maxPoolSize?"} F -->|是| G["创建非核心线程执行任务"] F -->|否| H["执行拒绝策略"] C --> I[完成] E --> J{"核心线程
超时检测"} J -->|未超时| E J -->|已超时| K["回收线程"] G --> I H --> L["记录异常 / 告警"] ``` **流程解读**: 1. **第一步**:如果当前运行线程数小于 `corePoolSize`,即使有空闲线程也创建新线程执行任务(除非设置了 `allowCoreThreadTimeOut`)。 2. **第二步**:如果线程数已达核心数且队列未满,将任务放入工作队列排队。 3. **第三步**:如果队列已满但线程数未达到 `maximumPoolSize`,创建非核心线程处理。 4. **第四步**:如果线程数达到上限且队列仍满,触发拒绝策略。 > [!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` | 优先队列 | 支持自定义优先级排序 | ```java // 推荐的线程池配置示例 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。多余线程只会增加上下文切换开销,不会提升吞吐。 ## 实践场景 **常见反模式排查清单**: 1. 用 `Executors.newFixedThreadPool()` 接高并发请求 → 换为有界队列的 `ThreadPoolExecutor` 2. 用 `new CachedThreadPool()` 处理持久化任务 → 缓存池会在空闲 60s 后回收所有线程,反复创建带来额外开销 3. 任务没有设置超时机制 → 配合 `Future.get(timeout)` 或CompletableFuture.orTimeout() ## 关联笔记 - [[01.Java/concurrent/锁升级与 CAS 机制]]