JVM 线程池扩容机制

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

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

因此:

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

当线程数量过多时:

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

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

ThreadPoolExecutor 核心参数

参数 类型 说明
corePoolSize int 核心线程数,线程池中长期保留的线程数量
maximumPoolSize int 最大线程数,当任务队列满时允许创建的最大线程数量
keepAliveTime long 非核心线程的最大空闲存活时间
unit TimeUnit keepAliveTime 的时间单位
workQueue BlockingQueue 任务队列,用于存放等待执行的任务
threadFactory ThreadFactory 线程创建工厂,用于自定义线程名称、优先级等
handler RejectedExecutionHandler 拒绝策略,当任务无法被执行时的处理方式

线程池的任务处理流程

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

先使用核心线程执行任务

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

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

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

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

队列已满 → 扩容线程池

如果:

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

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

这些线程称为 非核心线程

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

如果:

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

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

常见策略:

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

keepAliveTime 的作用

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

因此:

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

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

一个非常容易踩的坑

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

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

因此:

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

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