Skip to content

在 Java 的 IO 体系中,NIO(New IO / Non-blocking IO)是一个绑不开的话题。它是 Java 1.4 引入的一套新的 IO API,也是 Netty、Tomcat 等高性能框架的底层基石。

一、NIO 的三大核心组件

NIO 有三个最重要的概念:Channel、Buffer、Selector。我们用最通俗的方式来理解它们。

1. Channel(通道)

Channel 可以理解为一条双向的水管

在传统的 BIO 中,我们用 InputStream(只能读)和 OutputStream(只能写),就像两根单向的管子。而 Channel 是双向的,既能读也能写。

那么 Channel 两端连接的是什么呢? 这取决于 Channel 的类型:

  • SocketChannel 连接的是客户端与服务器,这是最经典的网络通信场景。当你用浏览器访问网站,或者 App 请求后端接口时,底层就是通过 SocketChannel 进行通信。

  • FileChannel 连接的是程序与本地文件,用于读写文件,比如读取配置文件、写日志、处理大文件上传下载等场景。

  • ServerSocketChannel 比较特殊,它本身不传输数据,而是用来监听端口、接受连接。每当有客户端连进来,它就会生成一个新的 SocketChannel 来处理这个连接。你可以把它想象成"守在门口等人来"的角色。

实际开发中,SocketChannelServerSocketChannel 这对组合用得最多,基本上所有网络通信框架的底层都是基于它们实现的。

2. Buffer(缓冲区)

Buffer 可以理解为一个临时存放数据的容器,就像一个水桶。

Channel 不能直接读写数据,必须通过 Buffer 中转。就像你用水管浇花,需要先用桶接水,再把桶里的水倒出去。

读数据时,数据从 Channel 流入 Buffer,再由程序从 Buffer 中取出。写数据时,程序先把数据写入 Buffer,再由 Buffer 流向 Channel。

Buffer 有几个重要的属性:capacity 表示容量,也就是这个桶有多大;position 表示当前位置,即读写进行到哪里了;limit 表示限制,即最多能读写到哪个位置。

3. Selector(选择器)

Selector 可以理解为一个前台接待员,这是 NIO 最强大的地方。

传统 IO 中,一个线程只能处理一个连接。但有了 Selector,一个线程就可以监控多个 Channel。就像一个前台接待员可以同时盯着多个门口,哪个门口有客人来了就去处理哪个。

它的工作方式是这样的:首先把多个 Channel 注册到一个 Selector 上,然后 Selector 会持续监控这些 Channel。当某个 Channel 有事件发生(比如有数据可读)时,Selector 会通知程序,程序只需要处理那些"有事"的 Channel 即可。

Selector 本质上就是 Java 对操作系统 IO 多路复用机制的封装。 在不同操作系统上,它依赖不同的底层实现:Linux 上使用 epoll,macOS 和 BSD 上使用 kqueue,Windows 上使用 select 或 IOCP。

这三个组件的协作关系可以这样理解:Selector 负责监控多个 Channel,当某个 Channel 有事件发生时,数据通过 Buffer 进行中转,最终到达程序进行处理。

简单总结一下:Channel 是数据的传输通道,就像双向水管;Buffer 是数据的临时存储,就像水桶;Selector 是管理多个 Channel 的调度者。

二、服务器并发模型的演进

理解了 NIO 的核心组件后,我们再来看看服务器处理并发连接的演进历史。这个演进过程能帮助你理解为什么 NIO 和 Selector 如此重要。

阶段一:多线程模型

最早的做法非常简单粗暴:每来一个客户端连接,就创建一个新线程去处理,处理完后线程销毁。

这种模型的优点是简单直观、代码好写。但问题也很致命:线程创建和销毁的开销很大,每个线程默认占用 1MB 左右的栈空间。如果有 1 万个连接就需要 1 万个线程,系统资源会直接耗尽。

这种模型只适用于连接数很少的小应用,比如几十个连接的场景。

阶段二:线程池模型

为了解决频繁创建销毁线程的问题,人们引入了线程池。做法是提前创建好一批线程放在池子里,有连接来了就从池子里拿一个线程去处理,用完后归还到池子里复用。

这种模型避免了频繁创建销毁线程的开销,也可以通过控制池子大小来防止资源耗尽。

但问题仍然存在:线程数量有限,比如池子里有 200 个线程,第 201 个连接就得排队等待。更关键的是,如果某个连接处理很慢(比如在等待 IO),线程就被占用着,其他连接只能干等。本质上还是一个线程同一时间只能处理一个连接。

这种模型适用于中等并发量的场景,大概几百到几千个连接。

阶段三:Selector 模型

Selector 模型彻底改变了思路:用一个线程(或少量线程)通过 Selector 监控所有连接,哪个连接"有事"就处理哪个。

核心思想不再是"一个线程守一个连接",而是"一个线程管一堆连接,谁有事就处理谁"。

这种模型的优势非常明显:少量线程就能处理海量连接,线程不会被阻塞在某个空闲的连接上,资源利用率极高。它适用于高并发场景,可以轻松支撑几万、几十万甚至百万级别的连接。

三个阶段对比

模型线程数能撑多少连接典型应用
多线程连接数 = 线程数几十个学习demo、小工具
线程池固定数量几百~几千传统Tomcat、早期Web服务
Selector很少(1~几个)几万~百万Netty、Nginx、Redis、Node.js

三、总结

NIO 的核心就是三大组件和一种思想。三大组件是 Channel(双向通道)、Buffer(数据缓冲)、Selector(多路复用)。一种思想是用少量线程管理大量连接,从"一对一"进化到"一对多"。

这就是为什么 Netty、Nginx、Redis、Node.js 等现代高性能框架和中间件都选择了 IO 多路复用模型。理解了 NIO,就理解了高并发网络编程的基石。