基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Tomcat NIO 的工作原理(Acceptor, Poller, Worker 线程的作用)

知识点图片

Tomcat 的 NIO(Non-blocking I/O)模型是其能够处理高并发连接的核心机制。相比于传统的 BIO(Blocking I/O,即 JIoEndpoint,Tomcat 8.5+ 已移除),NIO 模型通过多路复用(Multiplexing)技术,用少量的线程就能管理大量的连接。

Tomcat NIO 的核心处理类是 NioEndpoint。整个处理流程主要由三个核心组件协作完成:Acceptor(接收器)Poller(轮询器)Worker(工作线程/Executor)

我们可以用一个餐厅的例子来类比:

  • Acceptor:门口的迎宾,只负责把客人(连接)领进门。
  • Poller:大堂经理/服务员,不断巡视所有桌子,看哪桌客人准备点菜了(数据可读)。
  • Worker:后厨厨师,负责真正的做菜(处理业务逻辑)。

1. 核心组件详解

A. Acceptor(接收线程)

  • 数量:通常为 1-2 个线程(默认 1 个)。
  • 职责
    • 它是 TCP 连接的入口。
    • 它在一个 while(true) 循环中,主要执行 serverSocket.accept()
    • 注意:这一步是阻塞的,它会一直等到有新的 TCP 连接进来。
  • 工作流程
    1. 当新的连接建立(三次握手完成),accept() 返回一个 SocketChannel
    2. Acceptor 将这个 Channel 设置为非阻塞模式(Non-blocking)。
    3. 它将这个 Channel 封装成 NioChannel 对象。
    4. 关键动作:Acceptor 将这个对象转交给 Poller 处理(通常是将事件放入 Poller 的事件队列中)。
    5. Acceptor 立刻回到循环顶部,继续等待下一个连接。

B. Poller(轮询线程)

  • 数量:通常为 Math.min(2, Runtime.getRuntime().availableProcessors()),即最多 2 个。
  • 职责
    • 持有 Java NIO 的核心组件 Selector(选择器)
    • 负责管理所有已连接但尚未断开的 Socket。
    • 负责监听 Socket 上是否有数据到来(OP_READ)或是否可写(OP_WRITE)。
  • 工作流程
    • Poller 运行在一个 while(true) 循环中,主要做两件事:
      1. 处理事件队列:查看 Acceptor 是否塞给了它新的连接。如果有,将新连接注册到自己的 Selector 上,并关注 OP_READ 事件。
      2. Select(轮询):执行 selector.select()。这是一个阻塞操作(通常设置超时时间),它会询问操作系统:“我管理的成千上万个连接中,有哪些已经收到了数据包?”
    • 一旦 select() 返回,说明有一个或多个 Socket 准备好了(例如 HTTP 请求报文已经到达网卡缓冲区)。
    • 关键动作:Poller 从 Selector 中取出这些 Key,生成一个任务对象(SocketProcessor),然后将这个任务扔给 Worker 线程池

C. Worker(工作线程池 / Executor)

  • 数量:这是一个线程池,默认核心线程数 10,最大线程数 200(可在 server.xml 中配置 maxThreads)。
  • 职责
    • 执行真正的业务逻辑(Servlet/Spring MVC 等)。
    • 读取 Socket 中的数据(此时数据已经准备好,读取速度极快),解析 HTTP 协议,执行 Filter 链,调用 Servlet,生成响应。
  • 工作流程
    1. 从线程池中取出一个空闲线程。
    2. 执行 Poller 递过来的 SocketProcessor 任务。
    3. 读取数据:调用 socket.read()。因为 Poller 保证了只有数据到了才会叫 Worker,所以这里几乎不会阻塞在“等待数据网络传输”上。
    4. 业务处理:执行你的 Java 代码(查数据库、计算等)。注意:这里通常是阻塞的
    5. 发送响应:处理完后,将响应数据写入 Socket。

2. 整体工作流程图解

plaintext
Client (Browser)
      |
      | [1. TCP Connect]
      v
+----------------+
|    Acceptor    |  <-- 线程数:1
| (ServerSocket) |  <-- 作用:只负责建立连接,不做业务,极快
+-------+--------+
        |
        | [2. Pass SocketChannel (NioChannel)]
        v
+-------+--------+      [3. Register OP_READ]
|     Poller     |  <-- 线程数:1~2
|   (Selector)   |  <-- 作用:持有 Selector,轮询所有连接的数据状态
+-------+--------+
        |
        | [4. Data Arrived! (OP_READ ready)]
        | [5. Create SocketProcessor task]
        v
+-------+--------+
|  Worker Pool   |  <-- 线程数:200 (默认)
|   (Executor)   |  <-- 作用:解析 HTTP,执行 Servlet,查库,响应
+----------------+

3. 为什么要这样设计?(NIO vs BIO)

传统 BIO 的问题

在 BIO 模式下,Acceptor 接收到连接后,必须立刻分配一个 Worker 线程全程陪同。

  • 如果客户端连上了但迟迟不发数据(比如网络慢,或者 Keep-Alive 保持连接),这个 Worker 线程就会阻塞在 read(),傻傻地等待。
  • 后果:如果有 10000 个连接,就需要 10000 个线程。线程切换开销巨大,内存瞬间爆炸。

NIO 的优势

在 NIO 模式下,Poller 充当了中间人。

  • Poller 一个人(一个线程)就可以看着 10000 个连接。
  • 只有当某个连接真的有数据到了,Poller 才会去叫 Worker 线程。
  • 结果
    • Worker 线程不再把时间浪费在“等待网络数据传输”上。
    • Worker 线程一上来就是干活(读内存数据、跑业务)。
    • Tomcat 可以用 200 个 Worker 线程支持 10000+ 的并发连接(只要这些连接不是同时都在发数据)。

4. 总结

  1. Acceptor:负责连接(TCP Handshake)。
  2. Poller:负责中断/事件(等待数据到达,Selector 多路复用)。
  3. Worker:负责计算/业务(解析 HTTP,执行 Servlet)。

这种分层解耦的设计,使得 Tomcat 能够在高并发场景下,极大程度地降低线程消耗,提升吞吐量。

00:00
00:00