为什么 Tomcat 默认最大线程数是 200,而不是 N+1?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

为什么 Tomcat 默认最大线程数是 200,而不是 N+1?

面试考察点

  1. 线程模型理解深度:面试官不仅仅是想知道 Tomcat 的默认配置值,更是想考察你是否理解 "线程数 ≠ CPU 核心数" 这件事背后的本质——Tomcat 处理的是 IO 密集型任务,不是 CPU 密集型任务。

  2. 理论与实践结合能力:N+1 是 CPU 密集型的经验公式,但 Tomcat 是 Web 服务器,处理的是 HTTP 请求(大量 IO 等待)。面试官想看你是否能区分场景,而不是机械套公式。

  3. 系统调优意识:进一步考察你对 Tomcat 线程池模型、Connector 架构、以及生产环境如何根据业务特点调优的理解。

核心答案

直接说结论:Tomcat 默认 maxThreads=200,是因为它处理的是 IO 密集型任务,线程大部分时间在等待(等数据库返回、等网络 IO、等下游服务响应),而不是在疯狂吃 CPU。

N+1 那个公式只适用于 CPU 密集型任务。两者的本质区别:

维度 CPU 密集型 IO 密集型(Tomcat 场景)
瓶颈 CPU 算力 IO 等待(网络、磁盘、数据库)
线程状态 大部分在 RUNNING 大部分在 WAITING / BLOCKED
线程数经验值 N+1 2N 或更高,甚至几十倍于核心数
典型场景 加密计算、压缩、排序 Web 请求处理、RPC 调用、文件读写

Tomcat 选 200 这个值,是 Apache 团队经过大量压测和线上验证后得出的一个通用安全默认值,适用于绝大多数中等负载的 Web 应用。

深度解析

一、一个请求在 Tomcat 里到底在干嘛?

先搞清楚一件事:Tomcat 里的线程,绝大部分时间并不是在 "跑代码",而是在 "等"。

上图展示了一个典型 HTTP 请求的处理过程。关键点:

  • 一个请求从进来到响应回去,真正占用 CPU 的时间可能只有几毫秒,但整个请求耗时可能是 50ms、200ms 甚至几秒
  • 剩下的时间线程都处于 WAITING 状态(等数据库、等网络、等磁盘),这时候它 不消耗 CPU
  • 既然不消耗 CPU,那多开一些线程就不会造成 CPU 争抢,反而能提高吞吐量——一个线程等 IO 的时候,CPU 可以去跑另一个线程的业务逻辑

二、为什么 N+1 在这里不适用?

N+1 的前提假设是:线程几乎一直在用 CPU。比如你用多线程做加密计算、排序、视频编码,线程一刻不停地在算,这时候开太多线程反而有害——线程上下文切换的开销会吃掉 CPU 时间。

但在 Tomcat 里,情况完全不同:

// 一个典型的 Spring Controller 方法
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    // 线程大部分时间卡在这一行——等数据库返回
    // 这期间线程处于 WAITING 状态,不占 CPU
    return userService.findById(id);
}

假设一个请求总共耗时 100ms,其中查数据库花了 90ms,真正 CPU 运算只有 10ms。那你 8 核机器开 200 个线程完全没问题——因为同一时刻可能只有十几个线程在用 CPU,剩下 180 多个都在等 IO。

三、200 这个数字怎么来的?

Apache 团队选 200 作为默认值,考虑了这些因素:

  • 通用性:作为一个开箱即用的 Web 服务器,默认值要能覆盖绝大多数中等负载场景
  • 上下文切换的平衡点:线程数太少,吞吐量上不去;线程数太多,上下文切换开销反而拖慢系统。200 是一个经验上的 "甜点值"
  • 内存开销可控:Linux 默认线程栈大小约 1MB,200 个线程约占 200MB 栈内存,对现代服务器来说完全可接受
  • 历史经验:这是 Apache HTTP Server、Tomcat 等 Web 服务器多年生产验证的结果

当然,200 不是万能的。如果你的应用是 CPU 密集型(比如在 Servlet 里做大量计算),那 200 就太多了;如果你的应用 IO 等待非常严重(比如每次请求要调 5 个下游服务,每个耗时 500ms),那 200 可能还不够。

四、Tomcat 线程模型 vs JDK 线程池

这里有个很容易忽略的点:Tomcat 的线程池(org.apache.tomcat.util.threads.ThreadPoolExecutor)和 JDK 的 java.util.concurrent.ThreadPoolExecutor 不一样

上图对比了两种线程池的核心差异。关键区别在于:

  • JDK 线程池:核心线程满了 → 先塞队列 → 队列满了才创建非核心线程。这意味着如果用了无界队列(比如 LinkedBlockingQueue),最大线程数形同虚设,永远不会创建非核心线程
  • Tomcat 线程池:核心线程满了 → 先塞队列 → 队列满了立刻创建非核心线程(重写了 execute() 方法)。这样能更快地利用空闲线程处理突发流量

这个设计差异的背后逻辑是:JDK 线程池是通用设计,面向的是任务提交者;Tomcat 线程池是专门为 HTTP 请求设计的,面向的是高并发短任务场景,需要更积极的线程创建策略。

五、生产环境怎么调?

别迷信任何公式,压测才是王道。但可以参考以下思路:

<!-- server.xml 中的 Connector 配置 -->
<Connector port="8080"
           protocol="HTTP/1.1"
           minSpareThreads="10"     <!-- 最小空闲线程数 -->
           maxThreads="200"         <!-- 最大线程数,根据压测调整 -->
           acceptCount="100"        <!-- 等待队列长度 -->
           connectionTimeout="20000"
           maxConnections="8192"    <!-- 最大连接数,默认 8192(NIO)-->
/>
场景 建议配置 理由
CPU 密集型接口 maxThreads = 2N ~ 4N 计算密集,线程不宜过多
普通 CRUD 业务 maxThreads = 200 ~ 500 默认值即可,IO 等待占比高
重 IO 接口(调多个下游) maxThreads = 500 ~ 1000 等待时间长,需要更多线程支撑吞吐量
低延迟要求 配合异步 Servlet 用 NIO + 异步处理减少线程占用

面试高频追问

  1. 追问一:Tomcat 的 maxConnectionsmaxThreads 有什么区别?

    maxConnections 是 Tomcat 在 TCP 层面能同时接受的最大连接数(NIO 模式默认 8192),而 maxThreads 是实际处理业务逻辑的线程数。一个连接建立后,如果所有线程都在忙,就会放进 acceptCount 队列里等着。所以 maxConnectionsmaxThreads 是合理配置。

  2. 追问二:如果线程数设太小会有什么问题?

    请求会在 acceptCount 队列里堆积,队列也满了就直接返回连接被拒绝。监控上会看到响应时间飙升、吞吐量上不去。所以核心思路是:通过压测找到吞吐量和响应时间的最佳平衡点。

  3. 追问三:Tomcat 支持 NIO 之后,线程模型有什么变化?

    NIO 模式下(protocol="org.apache.coyote.http11.Http11NioProtocol"),Tomcat 用少量的 Acceptor 线程 + Poller 线程来处理连接和 IO 读写,只有当请求需要交给 Servlet 处理时才从线程池取一个业务线程。这样进一步降低了线程和连接的 1:1 绑定关系,提升了资源利用率。

常见面试变体

  • "Tomcat 的 maxThreads 设成多少合适?为什么?"
  • "Web 服务器和计算密集型应用,线程池配置有什么区别?"
  • "Tomcat NIO 模式下,连接数可以远大于线程数,原理是什么?"

记忆口诀

CPU 密集 N+1,IO 密集可以高,Tomcat 默认二百够用,生产调优靠压测。

总结

Tomcat 默认 200 个线程,本质上是因为 Web 请求是 IO 密集型任务,线程大部分时间在 "等" 而不是在 "算",所以可以远超 CPU 核心数。N+1 是给 CPU 密集型准备的公式,别乱套。生产环境怎么配?压测说话,别迷信公式。