【Java并发】Tomcat 与 Spring:后端项目中的线程与资源管理

文章目录

    • 从基础到高层:一条架构线
    • 一、核心关系与"资源"指什么
    • 二、后端项目对线程与资源的管理(总览)
      • [1. 谁在管理线程](#1. 谁在管理线程)
      • [2. 请求与线程、资源的对应关系](#2. 请求与线程、资源的对应关系)
    • [三、Tomcat 与 Spring 的线程模型交互(重点)](#三、Tomcat 与 Spring 的线程模型交互(重点))
      • [1. Tomcat 的线程池(底层)](#1. Tomcat 的线程池(底层))
      • [2. Spring 对线程的扩展(上层)](#2. Spring 对线程的扩展(上层))
        • [Spring 会不会自己创建线程池?(重要)](#Spring 会不会自己创建线程池?(重要))
      • [3. 资源在线程间的归属关系](#3. 资源在线程间的归属关系)
    • [四、Tomcat 启动 Spring 应用的完整流程](#四、Tomcat 启动 Spring 应用的完整流程)
    • 五、企业级配置要点(线程与资源管理)
      • [1. 调优 Tomcat 底层线程池(application.properties)](#1. 调优 Tomcat 底层线程池(application.properties))
      • [2. Spring 异步线程池配置(统一管理异步任务线程)](#2. Spring 异步线程池配置(统一管理异步任务线程))
      • [3. 保证异步场景下请求上下文不丢失(资源在线程间的传递)](#3. 保证异步场景下请求上下文不丢失(资源在线程间的传递))
      • [4. 连接池与线程数的匹配(资源与线程的配比)](#4. 连接池与线程数的匹配(资源与线程的配比))

从基础到高层:一条架构线

理解线程与资源管理,可以按五层递进:谁在跑请求 → 资源归谁管 → 谁创建/扩展线程 → 跨线程如何传上下文 → 如何配置与排查。

要回答的问题
运行环境与业务应用 谁接收请求、谁执行业务?Tomcat 是运行环境,Spring 是业务应用,请求先经 Tomcat 再经 Spring。
线程与资源对应 一个请求几条线程?资源归谁管?典型模型:一请求一 Tomcat 工作线程;资源分请求级/连接池/共享。
线程模型交互 线程谁创建?Spring 会不会起池?只有 Tomcat 有请求处理线程池;@Async 时若不配置则每任务一线程(非池),配置后才是线程池。
资源归属与传递 Request 跟谁走?异步为何拿不到?请求级资源跟线程走(ThreadLocal);异步需 TaskDecorator 或手动传递并清理;连接池与 maxThreads 匹配。
配置与可观测 参数怎么设?过载怎么拒?上下文怎么不丢?Tomcat 与 Spring 异步池显式配置;TaskDecorator 统一传上下文;统一命名便于 Dump 排查。

下文依此展开:先澄清 Tomcat 与 Spring 的角色及「资源」含义,再说明谁管线程、线程模型与 @Async 是否用池,最后是启动流程、企业级配置与总结。


一、核心关系与"资源"指什么

Tomcat 是 Servlet 容器,负责监听端口、接收连接、把请求交给工作线程;Spring (Context + Web MVC)是应用框架,负责 Bean 管理、请求分发、业务逻辑。Tomcat 是运行环境,Spring 是业务应用,Spring 应用跑在 Tomcat 里。

复制代码
1. HTTP 请求 → 
2. Tomcat(接收、分配线程)→ 
3. Spring MVC(DispatcherServlet)→
4. Spring 容器(Bean)→ 
5. 业务逻辑 → 
6. 响应

后端项目里的资源 大致分四类:请求级 (Request、Session 等,随请求生命周期);连接类 (DB/HTTP/Redis 连接池,按需获取与归还);缓存与共享 (需考虑并发安全);线程本身(Tomcat 工作线程、@Async 所用线程)。线程管理的核心是:谁创建线程、请求由哪条线程执行、资源如何获取与释放、请求结束后如何回收线程与上下文。


二、后端项目对线程与资源的管理(总览)

1. 谁在管理线程

层级 谁管理线程 管理什么
Tomcat 内置线程池(如 ThreadPoolExecutor 接收连接、把请求交给工作线程;maxThreadsminSpareThreadsacceptCount 等控制并发与排队。
Spring 请求处理不创建线程池,复用车载 Tomcat 线程 Controller/Service 在 Tomcat 线程中执行;@Async 时若不配置则每任务新起一线程(默认 SimpleAsyncTaskExecutor,非池),若显式配置 Executor 则使用该线程池。
业务/中间件 连接池、消息消费、定时任务等 数据库连接池内部可能有线程、消息监听器常自带线程池、Quartz 等有调度线程池。

2. 请求与线程、资源的对应关系

  • 一个请求 在典型场景下对应 一个 Tomcat 工作线程(从请求进入到响应返回)。
  • 该线程在执行过程中可能:从 连接池 取连接、从 RequestContextHolder 取当前请求上下文、从 Session 取用户信息;执行完毕后,连接归还池、线程归还 Tomcat 线程池,请求级上下文随之失效
  • 若使用 @Async ,主请求线程可提前释放,异步任务在另一条线程 执行(未配置 Executor 时是每任务一线程,配置了线程池则在该池中执行);此时若要用 Request/Session 等,需显式传递或恢复上下文,否则会丢。

一句话:后端通过"一个请求绑定一个工作线程"的模型,在该线程内完成对请求级资源与连接类资源的访问与释放;线程池参数和异步策略决定了并发能力和资源占用上限。


三、Tomcat 与 Spring 的线程模型交互(重点)

本节具体说明:Tomcat 如何提供请求处理线程池,Spring 如何在其之上做扩展。

1. Tomcat 的线程池(底层)

Tomcat 启动时会创建 内置线程池 (如 org.apache.tomcat.util.threads.ThreadPoolExecutor),核心作用:

  • 每个 HTTP 请求到来时,Tomcat 从线程池分配一个 工作线程 处理;
  • 该线程会一路贯穿到 Spring 层,Spring 业务逻辑(Controller/Service)默认就在这根线程里执行
  • 请求结束后,线程归还线程池,不会销毁,实现线程复用。

Tomcat 线程池关键参数(可在 server.xmlapplication.properties 配置):

  • maxThreads:最大工作线程数(默认 200),决定同时处理的最大请求数,也间接决定"最多有多少请求同时在用连接、缓存等资源"。
  • minSpareThreads:核心/常驻空闲线程数(默认 10),保持一定数量线程待命,减少冷启动延迟。
  • acceptCount :请求队列长度(默认 100),当工作线程全部占用时,新请求在此排队,排满则拒绝。

2. Spring 对线程的扩展(上层)

Spring 不会替换 Tomcat 的底层线程池,而是在其之上做扩展:

  • 默认场景 :Spring 业务代码(Controller/Service)都运行在 Tomcat 工作线程 中,无额外线程切换;请求级资源(Request、Session、RequestContextHolder)自然绑定当前线程。
  • 异步场景@Async / DeferredResult / CompletableFuture):耗时逻辑会放到另一批线程 执行,Tomcat 线程可提前释放;此时需注意 线程上下文传递,否则异步线程内拿不到当前请求的 Request/Session。
Spring 会不会自己创建线程池?(重要)

结论先说清:

场景 Spring 是否创建/使用线程池 说明
HTTP 请求处理(Controller/Service 同步执行) Spring 从不 为请求处理创建线程池,所有请求都在 Tomcat 工作线程 中执行,Spring 只是复用车载线程。
@Async 且未配置 Executor 不算"池" Spring 默认使用 SimpleAsyncTaskExecutor每个 @Async 任务新起一条线程 ,执行完即销毁,没有线程复用 ,本质是"每任务一线程"而非线程池。生产环境若大量使用 @Async 且不配置,会导致无界创建线程,有 OOM 风险。
@Async 且显式配置了 Executor (如 ThreadPoolTaskExecutor 此时异步任务提交到你配置的线程池 中执行,线程复用、数量可控。生产环境使用 @Async 时必须显式配置线程池 ,并指定 @Async("beanName")

线程上下文传递 :Spring 通过 RequestContextHolder (底层 ThreadLocal)保存当前请求的 RequestAttributes,仅当前请求所在线程可访问;异步线程中需通过参数传递或使用 TaskDecorator 包装,否则拿不到 Request/Session。

3. 资源在线程间的归属关系

资源类型 与线程的关系 谁管理生命周期
Request / Response / Session 处理该请求的线程绑定,通常一个请求一个线程 Tomcat + Spring(请求结束即失效)
RequestContextHolder ThreadLocal,仅当前线程可见 Spring(请求结束需清理,否则线程复用时可能串线)
数据库连接池 / 连接 线程从池中 get ,用毕 return,不"属于"某一线程 连接池(如 HikariCP、Druid)
异步线程池中的任务 由 Spring 或自定义 Executor 调度到 worker 线程 Spring / 自定义 Bean

四、Tomcat 启动 Spring 应用的完整流程

  1. Tomcat 启动 → 加载 web.xml 或 Spring Boot 自动配置的 DispatcherServlet(Spring MVC 的核心 Servlet)。
  2. Tomcat 初始化 DispatcherServlet → 触发 Spring 容器初始化ApplicationContext)。
  3. Spring 容器 扫描包、创建并初始化所有 Bean(Controller / Service / Mapper 等);若配置了连接池、异步线程池等,也在此阶段初始化。
  4. 初始化完成后,Tomcat 监听端口线程池就绪,等待请求。
  5. 请求到来 :Tomcat 从线程池取一条工作线程 → 调用 DispatcherServlet → Spring MVC 匹配 Controller → 执行业务逻辑(期间可能从连接池取连接、读 Request/Session)→ 返回响应 → 线程归还池、请求级资源失效

五、企业级配置要点(线程与资源管理)

结合"后端对线程与资源的管理",实际开发中需要关注以下配置。

1. 调优 Tomcat 底层线程池(application.properties)

Tomcat 线程池决定了 同一时刻最多有多少个请求在执行,也决定了连接池、缓存等资源的最大并发使用量。

properties 复制代码
# Tomcat 线程池配置
server.tomcat.max-threads=500       # 最大工作线程数(IO 密集型可设 200--500)
server.tomcat.min-spare-threads=50  # 核心空闲线程数
server.tomcat.accept-count=200      # 队列长度(满则拒绝)
server.tomcat.threads.max-idle-time=60000  # 线程空闲超时(毫秒)
  • maxThreads 不宜远大于数据库连接池等资源的容量,否则会出现大量线程在等连接。
  • acceptCount 过大时,排队请求占用内存且 RT 变长,需配合监控与限流使用。

2. Spring 异步线程池配置(统一管理异步任务线程)

使用 @Async 时,必须显式配置线程池:Spring 默认使用 SimpleAsyncTaskExecutor(每任务新起一线程,不复用),生产环境会导致无界创建线程、有 OOM 风险。显式配置后便于控制并发度、统一线程命名,方便排查问题。

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean("asyncThreadPool")
    public Executor asyncThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);       // 核心线程数
        executor.setMaxPoolSize(50);        // 最大线程数
        executor.setQueueCapacity(100);    // 队列长度
        executor.setThreadNamePrefix("async-");  // 线程名前缀(便于 Dump 排查)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

// 使用时指定线程池
@Async("asyncThreadPool")
public void doAsyncTask() {
    // 耗时业务逻辑
}

3. 保证异步场景下请求上下文不丢失(资源在线程间的传递)

Spring 的 RequestContextHolder 基于 ThreadLocal,只对 当前线程 有效;异步线程拿不到主请求的上下文,需显式传递。

方式一:调用方传入上下文,在异步方法内设置

在主线程先取 RequestAttributes,通过方法参数传入异步方法,在异步方法开头调用 RequestContextHolder.setRequestAttributes(attributes, true),任务结束后 resetRequestAttributes()。注意:若主请求已结束,Request/Response 可能已失效,仅适合"主请求未结束、异步仅做并行计算"等场景。

方式二:使用 TaskDecorator 统一传递(推荐)

java 复制代码
@Bean("asyncThreadPool")
public Executor asyncThreadPool() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // ... 其他参数 ...
    executor.setTaskDecorator(runnable -> {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
                if (attributes != null) {
                    RequestContextHolder.setRequestAttributes(attributes, true);
                }
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    });
    executor.initialize();
    return executor;
}

所有通过该线程池提交的异步任务都会自动带上当前请求的上下文,任务结束后清理,避免线程复用时串线。

4. 连接池与线程数的匹配(资源与线程的配比)

  • 数据库连接池大小 :建议与 Tomcat maxThreads 和业务特点匹配;一般可设为略小于或等于"实际并发请求数",避免连接数过大压垮数据库。
  • 线程命名 :Tomcat、Spring 异步线程池、连接池内部线程(若有)建议统一命名规范,便于用 Thread Dump 区分请求线程、异步线程、连接池线程,快速做问题归因。

相关推荐
独自破碎E2 小时前
IDEA 提示“未配置SpringBoot配置注解处理器“的解决方案
java·spring boot·intellij-idea
yqd6662 小时前
RabbitMQ用法和面试题
java·开发语言·面试
2601_949809592 小时前
flutter_for_openharmony家庭相册app实战+照片详情实现
android·java·flutter
4311媒体网2 小时前
Libvio.link 页面布局与数据分布
java·php
奋斗的小方2 小时前
01 一文读懂UML类图:核心概念与关系详解
java·uml
长安城没有风2 小时前
Java 高并发核心编程 ----- 线程池原理与实践(上)
java·juc
Remember_9932 小时前
Spring 核心原理深度解析:Bean 作用域、生命周期与 Spring Boot 自动配置
java·前端·spring boot·后端·spring·面试
风流倜傥唐伯虎2 小时前
java多线程打印
java·多线程
80530单词突击赢2 小时前
云原生时代:.NET与Java的K8s进化论
java