Spring内置常见线程池配置及相关概念

1-线程池

线程池 = 线程的"复用工厂 + 任务调度系统",只是如何用有限线程,稳定地处理无限任务

2-为什么一定要线程池

当你不用线程池的时候:

  • 每个任务一个线程(new Thread),后果:任务多 → 线程多 → 上下文切换爆炸 / 内存耗尽 / OOM
  • 少量线程串行,后果:吞吐低 / 延迟高 / 系统假死

使用线程池只是在吞吐、延迟、资源之间做一个平衡

3-具体实现

使用的类为:

java 复制代码
java.util.concurrent.ThreadPoolExecutor

构造参数:

java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,// 核心线程数
    int maximumPoolSize,// 最大线程数
    long keepAliveTime,// 存活时间
    TimeUnit unit,// 时间单位
    BlockingQueue<Runnable> workQueue,// 任务队列
    ThreadFactory threadFactory,// 线程创建时的规则
    RejectedExecutionHandler handler// 拒绝策略
)

构造参数释义:

  • corePoolSize(核心线程数):线程池"常驻"的线程数量

  • workQueue(任务队列):决定线程池的行为

    常见队列类型为:

    队列 特点 后果
    LinkedBlockingQueue 无界 OOM风险
    ArrayBlockingQueue 有界 可控
    SynchronousQueue 不存任务 压力直接给线程
    PriorityBlockingQueue 优先级 顺序复杂
  • maximumPoolSize(最大线程数):线程池允许创建的最大线程数量

  • keepAliveTime(存活时间):非核心线程,空闲多久会被回收

  • RejectedExecutionHandler(拒绝策略):

    常见策略为:

    策略 行为
    AbortPolicy 抛异常(默认)
    CallerRunsPolicy 调用方线程执行(常用)
    DiscardPolicy 直接丢弃
    DiscardOldestPolicy 丢队列最老任务
  • threadFactory(线程的生产规则):

    java 复制代码
    // 线程池自己并不直接 new Thread(),会通过下方代码创建线程:
    threadFactory.newThread(runnable)
    // threadFactory 最重要的 4 个作用
      new ThreadFactoryBuilder()
        // 1.线程命名
        .setNameFormat("order-exec-%d") // 自动生成 order-exec-0, order-exec-1...
        // 2.设置是否为守护线程:一般主要的业务用,设置为false
        .setDaemon(false)
        // 3.设置优先级(使用较少)
        .setPriority(Thread.NORM_PRIORITY)
    	// 4.设置 UncaughtExceptionHandler:防止线程因异常而终止但不输出任何信息,处理未捕获的异常
    	.setUncaughtExceptionHandler((t, e) -> {
        	log.error("线程 {} 异常", t.getName(), e);
    	});

4-线程池特点

  1. 单个线程池实例,全局只初始化一次

  2. 线程池的数量全局不止一个:可以分出很多个,比如:定时任务线程池,web 容器线程池(Tomcat)等等。主要是控制允许多少个任务同时处于运行态/可运行态,设计线程池的大小需要根据设备为准

  3. 池的大小分为两种类型:

    • CPU密级型:很少阻塞、一直占CPU;线程基本都在使用cpu,所以线程数量基本契合cpu的配置即可

      复制代码
      线程数 ≈ CPU 核数 或 CPU 核数 + 1
      比如: 12 核 16 线程的服务器
      设置:12 ~ 13
    • IO密集型:大量等待、CPU空闲;所以线程数量多一些,尽量高效提高cpu利用率,但不能太多

      复制代码
      线程数 ≈ CPU核数 × (1 + IO等待时间 / 计算时间)
      简化:CPU 核数 × 2 ~ 4
      比如: 12 核 16 线程的服务器
      设置:24 ~ 48

5-线程池一般的分类

类型 是否 CPU 密集 示例
Web 请求 IO Controller
业务异步 混合 @Async
定时任务 IO / 混合 @Scheduled
CPU 密集 加解密

6-Spring内部存在的线程池

6-1.Web容器线程池(最重要)

复制代码
#Tomcat(默认)
server.tomcat.threads.max = 200

特点:

  • IO密集型
  • 每个HTTP请求一个线程
  • 线程大多数时间在等DB/Redis

6-2.定时任务线程池

java 复制代码
@Scheduled

默认:

  • 单线程
  • 所有定时任务串行

一般必须自己配

复制代码
TaskScheduler

6-3.@Async线程池

java 复制代码
@Async
// 每次 @Async 都可能新建一个线程,在默认配置下,本质行为 ≈ new Thread()

默认:

  • SimpleAsyncTaskExecutor
  • 不是真正线程池
  • 会无限创建线程
  • 生产环境必须自定义

PS:Redis/DB等外部中间件连接池的设置大小尽量要契合,关注 max-active 等连接数参数,确保它能匹配你的业务并发量

7-自己建立线程池的条件

凡是:异步、定时、非 HTTP 主流程都应该用"自定义的线程池"

8-新项目线程池调整

一个新的spring项目,有一些情况下需要调整

8-1.Tomcat线程池

默认情况:

yaml 复制代码
maxThreads = 200
minSpareThreads = 10
#Tomcat 线程数 ≈ 下游连接池总容量 × 1~2

示例:

yaml 复制代码
server:
  tomcat:
    threads:
      max: 200               # 最大工作线程数
      min-spare: 20          # 最小空闲线程
      max-connections: 10000 # 最大连接数(含等待)
      accept-count: 100      # 等待队列长度
    connection-timeout: 20000 # 连接超时(ms)

一般经验:

yaml 复制代码
# CPU密集型应用(计算为主)
最大线程数 ≈ CPU核心数 × (1~2)

# IO密集型应用(数据库、网络调用多)
最大线程数 ≈ CPU核心数 × (2~4) × (1 + 等待时间/处理时间)

# 混合型(通用经验值)
最大线程数 = CPU核心数 × 4
最小空闲线程 = CPU核心数 × 2

# 1核2G(低配)
server:
  tomcat:
    threads:
      max: 50-100
      min-spare: 5-10

# 2核4G(标准)
server:
  tomcat:
    threads:
      max: 100-150
      min-spare: 10-20

# 4核8G(中配)
server:
  tomcat:
    threads:
      max: 150-200    # 默认值刚好适合
      min-spare: 20-30

# 8核16G+(高配)
server:
  tomcat:
    threads:
      max: 200-400    # 需要调高
      min-spare: 30-50

8-2.@Scheduled定时任务

默认行为:所有 @Scheduled,共用 1 个线程,串行执行,一般情况下必改

java 复制代码
@Configuration
@EnableScheduling// 启用定时任务功能,允许使用@Scheduled注解
public class ScheduleConfig {

    // Spring会自动使用这个自定义的调度器而不是默认的
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(4);// 设置线程池大小为4个线程(通常建议2-8个)
        scheduler.setThreadNamePrefix("sched-");// 设置线程名称前缀,便于调试识别
        scheduler.setAwaitTerminationSeconds(30);// 设置关闭时等待时间30秒
        scheduler.setWaitForTasksToCompleteOnShutdown(true);// 应用关闭时等待任务执行完成
        return scheduler;
    }
}

8-3.@Async异步线程池

默认 @Async = 什么?

java 复制代码
SimpleAsyncTaskExecutor ≈ new Thread()

自定义异步线程池:

java 复制代码
@Configuration
@EnableAsync// 启用异步方法执行功能,配合 @Async 注解使用
public class AsyncConfig {

    @Bean("asyncExecutor")// 将方法返回的对象注册为名为 "asyncExecutor" 的 Spring Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(16);// 核心线程数
        executor.setMaxPoolSize(32);// 最大线程数:线程池允许的最大线程数量
        executor.setQueueCapacity(500);// 队列容量:任务队列最大容量,超出后创建新线程
        executor.setThreadNamePrefix("async-");// 创建的线程名称前缀,便于调试识别
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 当线程池满载时,由调用线程自己执行任务,起到缓压作用
        executor.initialize();
        return executor;
    }
}

// 具体使用:
@Async("asyncExecutor")
public void doSomething() {
    // 耗时操作将在独立线程中执行
}

线程分配策略:

  1. 优先使用核心线程: 初始阶段先创建16个核心线程
  2. 队列缓冲机制: 新任务先放入容量为500的任务队列
  3. 动态扩容: 队列满了才创建超过核心线程数的新线程
  4. 上限控制: 线程总数不超过32个

执行流程:

  • 任务量 ≤ 16个 → 使用核心线程
  • 任务量 > 16个 → 先排队等待
  • 队列满(>500个任务)→ 创建新线程直到达到32个上限
  • 达到上限后 → 调用者线程直接执行任务(拒绝策略)

8-4.数据库 / Redis

对齐连接数:

yaml 复制代码
# 数据库(HikariCP)
spring:
  datasource:
    hikari:
      maximum-pool-size: 10 ~ 20

# Redis(Lettuce)
spring:
  redis:
    lettuce:
      pool:
        max-active: 8 ~ 16
#数据库连接数 = min(数据库限制 ÷ 实例数, CPU核心数 × 10)
#Redis连接数 = CPU核心数 × 2

8-5.其他

其实正常项目还会创建一些其他的线程池,除了Spring自带的Tomcat、定时任务、异步任务的线程池,一般还会创建:

  • 通用业务异步线程池,1个,用于:通知、事件、异步处理

  • 外部第三方接口线程池,1~2个,特点:慢、不稳定、有并发上限

  • CPU 密集型线程池,0或1个,用于:加密、大计算、规则引擎

  • 后台/低优先级线程池,1 个,用于:日志、统计、对实时性不敏感任务

9-多线程池好处

现在的系统中使用一个池,管理所有任务也是不可接受的

现在的系统一般都是使用多个线程池:

  • 资源隔离:

    bash 复制代码
    如果你只有一个线程池:
    第三方慢 → 线程占满 → HTTP 请求进不来 → 整个服务不可用
    --------------------
    线程池隔离之后:
    第三方池满 → 第三方失败
    主业务照常
  • 失败可控:

    复制代码
    有界线程池+拒绝策略:返回错误、快速失败、触发降级
  • 可观测性:

    bash 复制代码
    多个线程池意味着:
    async-biz-queue 爆了
    third-party-pool 耗尽
    cpu-pool 满负载
    --------------
    而不是:
    系统很慢,不知道为什么
相关推荐
Elias不吃糖2 小时前
Java 常用数据结构:API + 实现类型 + 核心原理 + 例子 + 选型与性能(完整版)
java·数据结构·性能·实现类
会游泳的石头2 小时前
构建企业级知识库智能问答系统:基于 Java 与 Spring Boot 的轻量实现
java·开发语言·spring boot·ai
新缸中之脑2 小时前
Google:Rust实战评估
开发语言·后端·rust
说给风听.2 小时前
Maven 配置实战:从环境搭建到 Idea 关联(超详细)
java·maven·intellij-idea
Hx_Ma162 小时前
SSM 项目中 mq是什么
java
生骨大头菜2 小时前
对接金蝶上传附件接口
java·开发语言
skywalker_112 小时前
File:路径详述
java·开发语言·file
老友@2 小时前
JMeter 在 Linux 环境下进行生产级性能压测的完整实战指南
java·linux·jmeter·性能优化·系统架构·压测·性能瓶颈
阿湯哥2 小时前
Reactor响应式编程中Sinks.Many
java·reactor