Tomcat线程模型与DeferredResult场景下的资源变化详解

一、Tomcat NIO线程模型核心组件(Tomcat 8+默认)

Tomcat从8.0版本开始默认使用NIO(非阻塞IO) 线程模型,这是理解连接、连接线程和工作线程关系的基础。整个模型由三个核心组件构成,各司其职:

1. 核心组件定义与职责

组件类型 线程名称 数量 核心职责 对应配置参数
Acceptor线程 http-nio-8080-Acceptor-0 1个/端口 监听TCP连接请求,建立Socket连接 acceptCount(等待队列长度)
Poller线程 http-nio-8080-ClientPoller-0 默认2个 监听已建立连接的IO事件(读/写) pollerThreadCount
工作线程(Executor) http-nio-8080-exec-* 核心10,最大200(默认) 执行应用层业务逻辑,处理请求和响应 threads.min-spare/threads.max

2. 组件关系图(文字描述)

复制代码
客户端
  |
  | TCP连接请求
  v
Acceptor线程(1个):接受连接,创建SocketChannel
  |
  | 注册到Poller
  v
Poller线程池(默认2个):多路复用监听所有Socket的IO事件
  |
  | 当有读事件时,将任务提交给
  v
工作线程池(默认最大200个):执行Servlet.service()方法,处理业务逻辑
  |
  | 处理完成后,将写事件注册回Poller
  v
Poller线程:监听写事件,准备发送响应
  |
  | 当Socket可写时,通知工作线程
  v
工作线程:将响应数据写入Socket
  |
  v
客户端

二、关键概念澄清:连接 vs 连接线程 vs 工作线程

1. 连接(TCP连接)

  • 本质:操作系统内核维护的一个Socket对象,代表客户端与服务器之间的一条通信链路
  • 占用资源:操作系统文件描述符(FD)、少量内核内存
  • 生命周期:从TCP三次握手完成开始,到四次挥手结束
  • 数量限制 :由Tomcat的server.tomcat.max-connections(默认8192)和操作系统的最大文件描述符限制共同决定

2. 连接线程(Acceptor+Poller)

  • 本质:Tomcat内部用于管理连接的专用线程
  • 特点:数量极少且固定,与并发连接数无关
  • 工作方式:使用IO多路复用(Java NIO Selector)技术,一个Poller线程可以同时监听数千甚至数万个连接的IO事件
  • 资源占用:每个线程占用约1MB栈内存,以及Selector对象的开销

3. 工作线程(Executor线程)

  • 本质:执行应用代码的线程池
  • 特点:数量可配置,是应用层并发能力的瓶颈
  • 工作方式:从队列中获取任务(请求处理),执行完成后返回线程池
  • 资源占用:每个线程占用约1MB栈内存,以及执行任务时的堆内存

4. 三者核心关系

  • 一个连接一个线程:在NIO模型下,数千个连接只需要2个Poller线程管理
  • 一个请求一个工作线程:在异步模型下,一个请求可以在多个工作线程上分段执行
  • 工作线程稀缺资源:默认只有200个,而连接可以有8192个
  • 连接线程充足资源:数量固定且极少,永远不会成为瓶颈

三、同步请求处理流程与资源变化

我们以一个典型的同步请求为例,详细说明每个阶段的资源占用情况:

阶段1:TCP连接建立

  • 客户端:发送SYN包
  • 服务器:Acceptor线程接受连接,创建SocketChannel
  • 资源变化:占用1个连接名额(max-connections减1),占用1个文件描述符
  • 线程占用:仅Acceptor线程(瞬间),无工作线程占用

阶段2:请求数据读取

  • Poller线程:监听到Socket的读事件
  • Poller线程:将"处理请求"任务提交给工作线程池
  • 工作线程:从线程池获取一个空闲线程,开始读取请求数据
  • 资源变化:占用1个工作线程
  • 线程占用:1个工作线程

阶段3:业务逻辑执行

  • 工作线程:调用DispatcherServlet,进入控制器方法
  • 工作线程:执行耗时操作(如数据库查询、RPC调用)
  • 资源变化:工作线程持续被占用,无法处理其他请求
  • 线程占用:1个工作线程(阻塞中)

阶段4:响应数据写入

  • 工作线程:生成响应数据,写入ServletResponse
  • 工作线程:将写事件注册到Poller
  • Poller线程:监听到Socket的写事件,通知工作线程
  • 工作线程:将响应数据从ServletResponse写入Socket
  • 资源变化:工作线程即将释放
  • 线程占用:1个工作线程

阶段5:请求处理完成

  • 工作线程:完成响应,调用Servlet.service()方法返回
  • 工作线程:释放,回到线程池
  • 资源变化:释放1个工作线程;连接可能保持(HTTP/1.1 keep-alive)或关闭
  • 线程占用:无工作线程占用

同步模型资源占用总结

  • 整个请求处理期间:始终占用1个连接名额
  • 从请求读取到响应完成:始终占用1个工作线程
  • 瓶颈:工作线程池大小(默认200),最多同时处理200个请求

四、DeferredResult异步请求处理流程与资源变化

现在我们来看使用DeferredResult时,各个阶段的资源变化情况,重点对比与同步模型的不同:

阶段1-2:TCP连接建立与请求读取

  • 与同步模型完全相同
  • 资源变化:占用1个连接名额,占用1个工作线程
  • 线程占用:1个工作线程

阶段3:控制器方法执行与DeferredResult返回

  • 工作线程:调用控制器方法
  • 控制器:创建DeferredResult对象,将其保存到某个地方(如队列、Map)
  • 控制器:立即返回DeferredResult对象
  • Spring MVC :调用request.startAsync(),启动异步上下文
  • Spring MVC:保存DeferredResult和AsyncContext,注册监听器
  • 工作线程:从控制器方法返回,DispatcherServlet处理完成
  • 资源变化释放1个工作线程!连接仍然保持
  • 线程占用无工作线程占用!这是异步模型的核心优势

阶段4:异步等待状态(最关键的阶段)

  • 状态:DeferredResult尚未被设置结果
  • 资源变化
    • ✅ 连接仍然占用1个连接名额
    • ✅ Poller线程仍然监听该连接的事件(如客户端断开)
    • 不占用任何工作线程
    • ❌ 不占用任何连接线程(Poller只是多路复用监听)
  • 线程占用零应用线程占用
  • 持续时间:可以是几毫秒到几十秒(取决于超时设置)

阶段5:业务逻辑执行与结果设置

  • 任意线程:执行业务逻辑(可以是自定义线程池、消息队列消费者、定时任务等)
  • 业务线程 :调用deferredResult.setResult(result)
  • DeferredResult:触发内部回调,通知Spring MVC结果已准备好
  • Spring MVC :将"渲染响应"任务重新提交到工作线程池
  • 资源变化:占用1个工作线程(从线程池重新获取)
  • 线程占用:1个工作线程(执行响应渲染)

阶段6-7:响应写入与处理完成

  • 与同步模型的阶段4-5完全相同
  • 工作线程:渲染视图,将响应数据写入ServletResponse
  • 工作线程:将写事件注册到Poller,最终写入Socket
  • 工作线程 :完成响应,调用asyncContext.complete()
  • 资源变化:释放1个工作线程;连接可能保持或关闭
  • 线程占用:无工作线程占用

五、同步 vs DeferredResult 资源变化对比表

处理阶段 同步模型 DeferredResult异步模型
连接建立 占用1个连接 占用1个连接
请求读取 占用1个工作线程 占用1个工作线程
业务执行 持续占用1个工作线程 释放工作线程,零占用
结果等待 持续占用1个工作线程 零工作线程占用
响应渲染 占用1个工作线程 重新占用1个工作线程
响应发送 占用1个工作线程 占用1个工作线程
处理完成 释放工作线程和连接 释放工作线程,连接可能保持
最大并发请求数 约等于工作线程数(200) 约等于最大连接数(8192)

六、DeferredResult场景下的线程状态时序图

复制代码
时间线:
0ms: 客户端发送请求
1ms: Acceptor接受连接,注册到Poller
2ms: Poller检测到读事件,提交任务到工作线程池
3ms: 工作线程T1开始处理请求
5ms: 控制器创建DeferredResult并返回
6ms: Spring启动异步上下文,工作线程T1释放回线程池
7ms: 【关键】进入异步等待状态,无工作线程占用
... 1000ms: 业务逻辑在自定义线程T2中执行
1007ms: T2调用deferredResult.setResult()
1008ms: Spring提交响应任务到工作线程池
1009ms: 工作线程T3(可能是不同的线程)开始渲染响应
1015ms: T3完成响应写入,释放回线程池
1016ms: 连接保持(keep-alive)或关闭

七、关键结论与常见误区澄清

1. 最核心的结论

  • DeferredResult释放的是工作线程,而不是连接
  • 在异步等待期间,连接仍然被占用,但不占用任何工作线程
相关推荐
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane199410 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
噢,我明白了11 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品11 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘11 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记11 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端