2026年7月11日 · 4 分钟阅读
Java IO 模型演进:BIO、NIO 与 AIO 的核心区别
梳理 Java 网络 IO 模型,从 BIO 阻塞通信到 NIO 的 Channel、Buffer、Selector,再到 AIO 的异步模型和适用场景。
Java 网络编程绕不开 IO 模型。BIO、NIO、AIO 不是三个孤立 API,而是网络连接和线程处理方式的演进。
理解它们的关键问题是:当一个连接没有数据可读、没有数据可写时,线程会发生什么?
BIO:阻塞式 IO
BIO 可以理解为 Blocking IO。JDK 1.4 之前,Java 网络通信主要使用 ServerSocket 和 Socket。
典型服务端流程:
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// read and write
}
问题在于:
accept()会阻塞,直到有客户端连接。read()会阻塞,直到有数据可读。- 一个线程同时只能处理一个连接的阻塞等待。
如果要支持多个客户端,通常要为每个连接创建一个线程。
BIO 的问题
BIO 模型简单,但连接数上来后问题明显。
| 问题 | 说明 |
|---|---|
| 线程数多 | 每个连接一个线程,资源占用高 |
| 上下文切换多 | 线程越多,调度成本越高 |
| 阻塞浪费 | 大量线程可能只是等待 IO |
| 扩展性差 | 很难支撑大量长连接 |
BIO 适合连接数较少、逻辑简单、并发压力不高的场景。
NIO:面向缓冲区的非阻塞 IO
NIO 是 New IO,也可以理解为 Non-blocking IO。它和 BIO 的目标一样,都是读写数据,但实现方式完全不同。
NIO 的三大核心组件是:
- Channel。
- Buffer。
- Selector。
BIO 面向流,NIO 面向缓冲区。BIO 一般是单向流,NIO 的 Channel 可以双向读写。
Channel
Channel 是通道,用来连接数据源和目标。
常见 Channel 包括:
FileChannel。SocketChannel。ServerSocketChannel。DatagramChannel。
Channel 不直接保存数据,读写数据都要经过 Buffer。
Buffer
Buffer 是缓冲区,本质上是一块可读写的内存。
NIO 读写流程通常是:
Channel -> Buffer -> 程序
程序 -> Buffer -> Channel
Buffer 里几个重要指针:
| 指针 | 含义 |
|---|---|
| capacity | 容量 |
| position | 当前读写位置 |
| limit | 当前可读写边界 |
| mark | 标记位置 |
写入 Buffer 后要调用 flip() 切换到读模式;读完后可以 clear() 或 compact() 继续写。
Selector
Selector 是 NIO 支撑高并发网络通信的关键。
一个 Selector 可以监听多个 Channel 的事件,比如:
- 连接就绪。
- 读就绪。
- 写就绪。
- 接收连接就绪。
这样一个线程就可以管理多个连接。线程不需要阻塞在某个连接上,而是等待“哪些连接已经准备好”。
NIO 网络通信流程
NIO 服务端通常按这个流程工作:
- 创建
ServerSocketChannel。 - 设置为非阻塞模式。
- 创建 Selector。
- 把 Channel 注册到 Selector,监听 accept 事件。
- Selector 轮询就绪事件。
- 有连接进来时,创建
SocketChannel。 - 把
SocketChannel注册到 Selector,监听 read/write 事件。 - 事件就绪后,通过 Buffer 读写数据。
这就是 Reactor 模式的基础。
BIO 和 NIO 对比
| 对比项 | BIO | NIO |
|---|---|---|
| IO 方式 | 阻塞 | 非阻塞 |
| 数据处理 | 面向流 | 面向缓冲区 |
| 线程模型 | 一个连接通常一个线程 | 一个线程可管理多个连接 |
| 适用场景 | 连接少、固定 | 连接多、轻量请求 |
| 编程复杂度 | 简单 | 较复杂 |
NIO 的优势不在于单次读写一定更快,而在于更适合大量连接。
AIO:异步 IO
AIO 是 Asynchronous IO。NIO 需要应用线程主动轮询事件,AIO 则更强调异步回调:操作系统完成 IO 后通知应用。
可以简单对比:
| 模型 | 线程行为 |
|---|---|
| BIO | 线程发起 IO 后阻塞等待 |
| NIO | 线程轮询就绪事件,准备好再读写 |
| AIO | 线程提交 IO 请求,完成后回调通知 |
AIO 理论上适合连接多、耗时长、需要异步通知的场景,但在 Java 服务端开发中,Netty 这类基于 NIO 的框架更常见。
IO 模型怎么选
可以按连接规模和请求特征选择:
| 场景 | 建议 |
|---|---|
| 少量连接、代码简单 | BIO |
| 大量连接、轻量请求 | NIO / Netty |
| 异步文件或特定系统能力 | AIO |
| 生产级高并发网络服务 | Netty |
工程中很少直接用原生 NIO 写复杂业务,因为 Selector、半包粘包、编解码、线程模型都要自己处理。Netty 正是为了降低这些复杂度。
小结
BIO、NIO、AIO 的区别可以这样记:
- BIO:一个连接阻塞一个线程,简单但扩展性差。
- NIO:一个线程管理多个连接,依赖 Channel、Buffer、Selector。
- AIO:提交异步 IO,完成后回调。
理解 IO 模型后,再看 Netty 的线程模型和 Pipeline,就能明白它为什么建立在 NIO 之上。