外观
一句话答案
BIO 同步阻塞一连接一线程,NIO 同步非阻塞基于 Selector 多路复用,AIO 异步非阻塞由 OS 回调通知。
核心要点
三种 IO 模型对比:
| 模型 | 全称 | 特点 | 适用场景 |
|---|---|---|---|
| BIO | Blocking IO(同步阻塞) | 一个连接一个线程,读/写时阻塞 | 并发量小、逻辑简单 |
| NIO | Non-blocking IO(同步非阻塞) | 多路复用,一个线程处理多连接 | 高并发,如 Netty |
| AIO | Asynchronous IO(异步非阻塞) | 内核通知应用数据已就绪 | Linux 支持有限,较少用 |
BIO 的问题:
java
// 每个连接占用一个线程
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞,等待连接
new Thread(() -> handle(socket)).start(); // 每连接开一个线程
}
// 10万并发 = 10万线程,内存/CPU 上下文切换开销巨大NIO 的核心:Selector(多路复用)
java
// 一个线程管理多个 Channel
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false); // 设置非阻塞
server.register(selector, SelectionKey.OP_ACCEPT); // 注册感兴趣的事件
while (true) {
selector.select(); // 阻塞,直到有事件就绪
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) { /* 处理连接 */ }
if (key.isReadable()) { /* 处理读事件 */ }
}
}NIO 的三大核心:
- Channel(通道):双向,支持非阻塞读写
- Buffer(缓冲区):读写数据的中间层,必须通过 Buffer 操作
- Selector(选择器):监听多个 Channel 的事件,事件就绪时通知应用
NIO 底层原理(Linux epoll):
select/poll(轮询):
每次调用将所有 fd 传入内核,内核遍历所有 fd 检查就绪状态
O(n) 遍历,fd 数量多时性能差
epoll(事件驱动):
epoll_create:内核创建 epoll 实例(红黑树 + 就绪队列)
epoll_ctl:将 fd 注册到内核红黑树中(只需注册一次)
epoll_wait:阻塞等待,内核将就绪 fd 放入就绪队列,直接返回就绪 fd
O(1) 等待(无论总 fd 数量多少,只返回就绪的)AIO(NIO 2):
- IO 操作完全由内核异步完成,完成后回调通知应用
- Java 的
AsynchronousSocketChannel实现 - Linux 的异步 IO(io_uring)支持有限,实际上 Java AIO 在 Linux 上底层仍用 epoll 模拟
- 实际项目中 Netty 等框架基于 NIO 即可满足需求
追问与易错
追问方向:
- NIO 的三大核心组件是什么?(Channel/Buffer/Selector)
- 为什么生产环境用 Netty 而不是原生 NIO?(API 复杂/epoll bug/半包粘包处理)
- AIO 在 Linux 上为什么推广不开?(Linux AIO 实现不完善,epoll 够用)
易错点:
- ❌ "NIO 就是非阻塞IO"——NIO 是 New IO,包含非阻塞+多路复用+Buffer
- ❌ 混淆 IO 多路复用和异步 IO——多路复用仍是同步的(就绪通知后自己读)
💡 记忆锚点
BIO = 餐厅一桌配一个服务员干等;NIO = 一个服务员盯着叫号屏(Selector/epoll),哪桌亮灯就去服务;AIO = 后厨做好直接端上桌,服务员连盯屏幕都省了。生产环境NIO+Netty是主流。