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

5.4 KiB
Raw Blame History

tags, create time, update time
tags create time update time
java/lang
thread-pool
ThreadPoolExecutor
rejection-policy
task-queue
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["记录异常 / 告警"]

流程解读:

  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 优先队列 支持自定义优先级排序
// 推荐的线程池配置示例
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()

关联笔记