跳到主要内容

JVM 线程池扩容机制

· 阅读需 3 分钟

HotSpot VM 的线程模型中,Java 线程与操作系统线程是一对一映射关系(JDK19 之后虽然引入了虚拟线程,但传统线程仍然是这种模型)。
也就是说,一个 Java 线程会对应一个操作系统内核线程(KLT / LWP)。

当 Java 创建线程时,需要调用操作系统内核 API 创建对应的内核线程,操作系统需要为其分配栈空间、调度信息等资源;当 Java 线程结束时,对应的内核线程也会被回收。

因此:

  • 线程创建和销毁成本较高
  • 线程数量不能无限增加

当线程数量过多时:

  1. 线程创建会带来较高的系统开销
  2. CPU 在多个线程之间频繁进行 上下文切换(Context Switch)
  3. 大量线程可能导致 系统性能下降甚至 OOM

因此,在实际开发中通常使用 线程池(ThreadPoolExecutor) 来统一管理线程生命周期。

ThreadPoolExecutor 核心参数

参数类型说明
corePoolSizeint核心线程数,线程池中长期保留的线程数量
maximumPoolSizeint最大线程数,当任务队列满时允许创建的最大线程数量
keepAliveTimelong非核心线程的最大空闲存活时间
unitTimeUnitkeepAliveTime 的时间单位
workQueueBlockingQueue任务队列,用于存放等待执行的任务
threadFactoryThreadFactory线程创建工厂,用于自定义线程名称、优先级等
handlerRejectedExecutionHandler拒绝策略,当任务无法被执行时的处理方式

线程池的任务处理流程

当有新的任务提交到线程池时,线程池会按照以下顺序处理:

1)先使用核心线程执行任务

如果当前运行线程数 小于 corePoolSize,线程池会直接创建新线程执行任务。

2)核心线程已满 → 任务进入队列

如果当前线程数 已经达到 corePoolSize

新任务不会立即创建线程,而是进入 BlockingQueue 任务队列 等待执行。

3)队列已满 → 扩容线程池

如果:

  • 任务队列已经达到最大容量
  • 且当前线程数 小于 maximumPoolSize

此时线程池会 创建新的线程处理任务

这些线程称为 非核心线程

4)超过最大线程数 → 拒绝策略

如果:

  • 队列已满
  • 当前线程数已经达到 maximumPoolSize

此时线程池会触发 RejectedExecutionHandler 拒绝策略

常见策略:

  • AbortPolicy(默认,直接抛异常)
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

keepAliveTime 的作用

当线程池中的 非核心线程keepAliveTime 时间内 没有执行任何任务 时,该线程会被销毁。

因此:

  • 核心线程:默认不会销毁
  • 非核心线程:空闲超过 keepAliveTime 会被回收

这样线程池可以在 高峰期扩容,在低负载时回收线程

一个非常容易踩的坑

如果 workQueue 使用 无界队列(如 LinkedBlockingQueue 默认构造)

  • 任务会一直进入队列
  • 队列几乎不会满

因此:

  • 线程池永远不会扩容
  • maximumPoolSizekeepAliveTime 基本不会生效

这也是很多线上系统 线程池配置失效 的常见原因。

评论 / COMMENTS