Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java多线程
  • Java线程锁
  • Java JMM
  • Java ThreadLocal
  • Java 线程池
    • 一、ThreadPoolExecutor:线程池的核心实现
      • 1.1 核心参数详解
      • (1)核心线程数(corePoolSize)
      • (2)最大线程数(maximumPoolSize)
      • (3)空闲存活时间(keepAliveTime + unit)
      • (4)工作队列(workQueue)
      • (5)线程工厂(ThreadFactory)
      • (6)拒绝策略(RejectedExecutionHandler)
      • 1.2 线程池核心工作流程
    • 二、拒绝策略:线程池的"安全阀"
      • 2.1 内置策略
      • 2.2 自定义策略
    • 三、线程池的状态:生命周期管理
    • 四、为什么不推荐使用Executors工具类?
      • 4.1 无界队列导致OOM(newFixedThreadPool / newSingleThreadExecutor)
      • 4.2 无限制创建线程导致资源耗尽(newCachedThreadPool)
    • 五、参数配置最佳实践
      • 5.1 核心线程数与最大线程数
      • 5.2 工作队列选择
      • 5.3 其他建议
    • 六、总结
  • Java CompletableFuture
  • 《多线程》笔记
Tavio
2022-04-20
目录

Java 线程池

在Java并发编程中,线程是稀缺且昂贵的系统资源。直接创建和销毁线程会带来三个核心问题:

  1. 资源消耗大:线程的创建/销毁涉及操作系统内核态与用户态的切换,频繁操作会占用大量CPU时间片,导致系统吞吐量下降。
  2. 稳定性风险:无限制创建线程会耗尽内存资源(每个线程默认栈大小1MB),最终触发OutOfMemoryError。
  3. 管理成本高:无法统一控制线程的生命周期、任务队列长度及并发数上限,易导致系统失控。

线程池的核心价值在于线程复用与统一管理:它像一个"线程工厂",提前创建并维护一批线程,任务提交时直接分配线程执行,避免重复创建销毁的开销,同时通过参数约束系统资源占用。

# 一、ThreadPoolExecutor:线程池的核心实现

ThreadPoolExecutor是Java线程池的核心实现类,其行为由七大核心参数共同决定。理解这些参数是掌握线程池的基础。

# 1.1 核心参数详解

public ThreadPoolExecutor(
    int corePoolSize,                      // 核心线程数
    int maximumPoolSize,                   // 最大线程数
    long keepAliveTime,                    // 非核心线程空闲存活时间
    TimeUnit unit,                         // 时间单位
    BlockingQueue<Runnable> workQueue,     // 工作队列
    ThreadFactory threadFactory,           // 线程工厂
    RejectedExecutionHandler handler       // 拒绝策略
)
1
2
3
4
5
6
7
8
9

# (1)核心线程数(corePoolSize)

  • 线程池的"常驻线程"数量,即使空闲也不会被销毁(除非通过allowCoreThreadTimeOut(true)开启核心线程超时销毁)。
  • 任务提交时的处理逻辑:
    • 若当前线程数 < corePoolSize:直接创建新线程(核心线程)执行任务。
    • 若当前线程数 ≥ corePoolSize:任务进入工作队列等待。

# (2)最大线程数(maximumPoolSize)

  • 线程池允许创建的最大线程总数(核心线程 + 非核心线程)。
  • 约束逻辑:当核心线程满、工作队列也满时,线程池会创建非核心线程执行任务,直到线程数达到maximumPoolSize。
  • 注意:corePoolSize ≤ maximumPoolSize。若两者相等,则线程池为"固定大小"(无临时线程)。

# (3)空闲存活时间(keepAliveTime + unit)

  • 非核心线程空闲超过该时间后,会被销毁以释放资源。
  • 特殊配置:通过allowCoreThreadTimeOut(true)可让核心线程也遵守该规则(适用于资源紧张场景,如临时任务高峰后释放资源)。

# (4)工作队列(workQueue)

  • 核心线程满后,新任务会先进入队列等待,而非直接创建非核心线程。
  • 常用队列类型及适用场景:
队列类型 特点 适用场景 风险提示
ArrayBlockingQueue 有界队列,需指定初始容量 生产环境首选,可控制任务堆积上限 容量需合理设置(过小易触发拒绝)
LinkedBlockingQueue 无界队列(默认容量Integer.MAX_VALUE) 不推荐!任务堆积易导致OOM 高并发下风险极高
SynchronousQueue 同步队列,不存储任务(直接传递) 任务需快速处理,不允许排队的场景 依赖最大线程数控制并发
PriorityBlockingQueue 优先级队列,按任务优先级排序 任务有明确优先级区分的场景(如紧急任务) 需自定义任务的compareTo方法

# (5)线程工厂(ThreadFactory)

  • 用于创建线程,可自定义线程名称、优先级、是否为守护线程等属性。
  • 默认工厂:Executors.defaultThreadFactory(),创建的线程为非守护线程,名称格式为pool-{poolNum}-thread-{threadNum}。
  • 自定义示例(便于问题排查):
  ThreadFactory customThreadFactory = new ThreadFactory() {
      private final AtomicInteger threadNum = new AtomicInteger(1);
      @Override
      public Thread newThread(Runnable r) {
          Thread thread = new Thread(r, "order-pool-thread-" + threadNum.getAndIncrement());
          thread.setDaemon(false); // 非守护线程(避免主线程退出后被强制终止)
          thread.setPriority(Thread.NORM_PRIORITY); // 正常优先级
          return thread;
      }
  };
1
2
3
4
5
6
7
8
9
10

# (6)拒绝策略(RejectedExecutionHandler)

  • 当线程池达到maximumPoolSize且工作队列已满时,新提交的任务会触发拒绝策略(线程池的最后一道防线)。

# 1.2 线程池核心工作流程

任务处理遵循核心线程 → 工作队列 → 非核心线程 → 拒绝策略的顺序,步骤如下:

  1. 提交任务时,若当前线程数 < corePoolSize:创建核心线程执行任务。
  2. 若核心线程满(≥ corePoolSize):任务进入工作队列等待。
  3. 若队列满且当前线程数 < maximumPoolSize:创建非核心线程执行任务。
  4. 若队列满且线程数 ≥ maximumPoolSize:执行拒绝策略。

示例:设corePoolSize=2,maximumPoolSize=4,workQueue=ArrayBlockingQueue(2)

  • 任务1、2:创建核心线程执行。
  • 任务3、4:进入队列等待。
  • 任务5、6:创建非核心线程执行(总线程数达4)。
  • 任务7及以后:触发拒绝策略。

# 二、拒绝策略:线程池的"安全阀"

ThreadPoolExecutor内置4种拒绝策略,同时支持自定义策略,需根据业务场景选择:

# 2.1 内置策略

  1. AbortPolicy(默认)
    • 直接抛出RejectedExecutionException,中断任务提交流程。
    • 适用场景:核心业务任务(需明确感知任务提交失败,避免静默丢失)。
  new ThreadPoolExecutor.AbortPolicy()
1
  1. CallerRunsPolicy
    • 由提交任务的线程(调用者线程)直接执行任务,会阻塞调用者。
    • 适用场景:并发量不大的场景或需要限流(通过阻塞调用者降低任务提交速度)。
  new ThreadPoolExecutor.CallerRunsPolicy()
1
  1. DiscardPolicy
    • 直接丢弃新任务,不抛异常(任务无声丢失)。
    • 适用场景:非核心任务(如日志采集、统计上报,丢失不影响核心流程)。
  new ThreadPoolExecutor.DiscardPolicy()
1
  1. DiscardOldestPolicy
    • 丢弃队列中最旧的任务,尝试重新提交当前任务。
    • 适用场景:任务有先后顺序,且旧任务可丢弃(如实时数据处理,旧数据时效性低)。
  new ThreadPoolExecutor.DiscardOldestPolicy()
1

# 2.2 自定义策略

实现RejectedExecutionHandler接口,可添加日志记录、任务持久化(如存入数据库/消息队列)等逻辑:

RejectedExecutionHandler customHandler = (runnable, executor) -> {
    // 记录被拒绝的任务信息
    log.error("任务被拒绝,当前线程池状态:活跃线程数={}, 队列大小={}, 任务详情={}",
            executor.getActiveCount(),
            executor.getQueue().size(),
            runnable.toString());
    // 可选:将任务存入Redis,后续重试
    redisTemplate.opsForList().leftPush("rejected_tasks", JSON.toJSONString(runnable));
};
1
2
3
4
5
6
7
8
9

# 三、线程池的状态:生命周期管理

ThreadPoolExecutor定义了5种状态,控制线程池的生命周期及任务处理行为:

状态 含义 触发方式
RUNNING 正常运行:接收新任务,处理队列中已有任务 线程池初始化后默认状态
SHUTDOWN 不接收新任务,但继续处理队列中已有任务 调用shutdown()
STOP 不接收新任务,中断正在执行的任务,清空队列 调用shutdownNow()
TIDYING 所有任务执行完毕,线程数为0,即将进入终止状态 线程池从SHUTDOWN/STOP过渡到此状态
TERMINATED 线程池彻底终止,执行完terminated()钩子方法(可自定义资源清理逻辑) 从TIDYING状态过渡,标志生命周期结束

状态转换流程:
RUNNING → SHUTDOWN → TIDYING → TERMINATED
或
RUNNING → STOP → TIDYING → TERMINATED

# 四、为什么不推荐使用Executors工具类?

Executors提供了线程池工厂方法(如newFixedThreadPool、newCachedThreadPool),但为了简化使用牺牲了可控性,存在潜在风险:

# 4.1 无界队列导致OOM(newFixedThreadPool / newSingleThreadExecutor)

  • 两者均使用LinkedBlockingQueue(默认容量Integer.MAX_VALUE,约20亿)。
  • 风险:当任务提交速度远大于处理速度时,任务会无限制堆积在队列中,耗尽堆内存触发OOM。

# 4.2 无限制创建线程导致资源耗尽(newCachedThreadPool)

  • 核心线程数=0,最大线程数=Integer.MAX_VALUE,空闲超时60秒。
  • 风险:高并发下会无限制创建线程,导致CPU上下文切换频繁、内存耗尽,甚至触发操作系统"进程内存限制"。

结论:生产环境应直接使用ThreadPoolExecutor,通过显式参数控制资源上限。

# 五、参数配置最佳实践

合理配置线程池参数是发挥其性能的关键,需结合任务类型(CPU密集/IO密集)和系统资源评估:

# 5.1 核心线程数与最大线程数

  • CPU密集型任务(如计算、排序、加密):
    线程数 = CPU核心数 + 1(+1是为了避免CPU空闲,当某线程因页缺失等阻塞时,其他线程可利用CPU)。
    示例:4核CPU → 5个线程。

  • IO密集型任务(如数据库查询、网络请求、文件IO):
    线程数 = CPU核心数 / (1 - 阻塞系数)(阻塞系数通常为0.8~0.9,即任务80%~90%时间在等待IO)。
    示例:4核CPU → 4 / (1 - 0.8) = 20个线程。

# 5.2 工作队列选择

  • 必须使用有界队列(如ArrayBlockingQueue),避免无界队列导致的OOM。
  • 队列容量需根据业务压测确定:过小易触发拒绝策略,过大则占用内存过多。建议初始值设为核心线程数的5~10倍,再通过监控调优。

# 5.3 其他建议

  • 线程工厂:自定义线程名称(如order-process-pool-thread-1),便于日志排查和监控。
  • 空闲时间:非核心线程空闲时间建议设为30~60秒(平衡资源释放与任务响应速度)。
  • 拒绝策略:核心任务用AbortPolicy(快速失败),非核心任务用自定义策略(日志+持久化)。

# 六、总结

线程池是Java并发编程的核心工具,其核心价值在于通过线程复用减少资源消耗,通过参数约束保证系统稳定。使用时需注意:

  1. 直接使用ThreadPoolExecutor,避免Executors工具类的隐藏风险。
  2. 根据任务类型(CPU/IO密集)合理配置核心参数,尤其是核心线程数、最大线程数和有界队列。
  3. 选择合适的拒绝策略,并通过监控及时发现线程池异常。
编辑 (opens new window)
#Java 线程池
上次更新: 2026/01/21, 19:29:14
Java ThreadLocal
Java CompletableFuture

← Java ThreadLocal Java CompletableFuture→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式