第一部分:宏观视角------什么是 I/O 模型?
在计算机世界里,I/O(Input/Output)即输入/输出。
-
从内核角度看 :所有的 I/O 最终都是在操作系统内核中进行的。对于一次网络输入,数据会先到达网卡 ,然后拷贝到内核空间 ,最后由应用程序从内核空间拷贝到用户空间。
-
从模型角度看 :I/O 模型解决的是"当数据还没准备好时,线程是等(阻塞)还是不等(非阻塞) ",以及"当数据准备好了,是谁负责搬运(同步还是异步)"的问题。
第二部分:四大模型深度对峙------从 BIO 到 AIO
1. BIO (Blocking I/O) - 同步阻塞
-
特征:一个连接对应一个线程。如果数据没来,线程就一直挂起。
-
瓶颈:线程资源昂贵。面对 10 万个并发连接,系统无法开 10 万个线程,必然崩溃。
-
适用:连接数少且固定的架构。
2. NIO (Non-blocking I/O) - 同步非阻塞
-
特征:线程发起请求后立即返回。如果数据没准备好,就不断轮询(Polling)。
-
瓶颈:轮询操作极度消耗 CPU,且大部分轮询是无效的。
3. I/O Multiplexing (I/O 多路复用) - 现代高并发的基石
这是 Redis、Nginx、Netty 的核心。它通过 select/epoll 系统调用,让一个线程同时监控成千上万个文件描述符(FD)。
-
select:线性扫描所有 FD,有上限限制(通常 1024),效率随连接数增加而下降。
-
epoll (Linux 2.6+):事件驱动。只有真正活跃的连接会触发回调,效率极高,没有连接上限。
4. AIO (Asynchronous I/O) - 异步非阻塞
-
特征 :应用告知内核"我要读数据",然后去忙别的。内核把数据准备好并拷贝到用户空间后,再通知应用。
-
现状:Java AIO 在 Windows 上支持良好,但在 Linux 上底层实现仍是 epoll 模拟,性能提升不明显,因此目前主流仍是 NIO。
第三部分:Java NIO 三剑客------Buffer、Channel、Selector
如果说 BIO 是基于"流(Stream)"的,那么 NIO 就是基于"块(Block)"的。
1. Buffer (缓冲区)
NIO 所有的操作都是通过缓冲区。它本质上是一个数组,但提供了 position、limit、capacity 三个指针来控制读写。
2. Channel (通道)
类似于 BIO 中的流,但 Channel 是双向的。既可以读,也可以写。
3. Selector (选择器)
Selector 是多路复用的核心。它能检测多个注册在上面的 Channel 是否有事件发生(如连接、读、写)。
💡 Java 代码演示:手写一个 NIO 服务端
Java
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false); // 必须设置为非阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞等待事件发生
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
// 处理新连接...
} else if (key.isReadable()) {
// 读取数据...
}
iter.remove(); // 必须移除,否则会重复触发
}
}
第四部分:面试复盘脑图
Code snippet
mindmap
root((Java IO 模型))
分类
BIO: 一连接一线程, 阻塞严重
NIO: 缓冲区+通道, 选择器多路复用
AIO: 事件+回调, 真正的异步
NIO 三要素
Buffer: 数据载体 (flip/clear/rewind)
Channel: 传输通道 (File/Socket/Datagram)
Selector: 多路复用器 (基于 epoll)
多路复用演进
Select: 线性轮询, 1024限制
Poll: 线性轮询, 无限制但慢
Epoll: 事件通知, 响应式, O(1) 复杂度
核心对比
阻塞 vs 非阻塞: 线程是否挂起
同步 vs 异步: 数据拷贝由谁完成 (内核 or 用户)
实战应用
Netty: 封装 NIO, 解决粘包/半包, Reactor 模式
Redis: 单线程多路复用
Kafka: 零拷贝 (sendfile) + NIO
第五部分:大厂面试官的"夺命三连问"
-
为什么 Netty 不使用 AIO 而是坚持使用 NIO?
-
回答要点:
-
Linux 平台下 AIO 底层依然是 epoll 封装,没有本质性能飞跃。
-
AIO 编程模型极其复杂。
-
NIO 配合 Reactor 模式已经足够支撑百万级并发,性价比更高。
-
-
-
epoll 的"水平触发(LT)"和"边缘触发(ET)"有什么区别?
-
回答要点:
-
LT (Level Trigger):只要缓冲区有数据,内核就不断提醒你。
-
ET (Edge Trigger):只有数据从无到有时才提醒一次。性能更高,但要求应用必须一次性把数据读完,否则容易丢失事件。
-
-
-
零拷贝(Zero-Copy)在 NIO 中是如何体现的?
- 回答要点 :NIO 提供了
DirectBuffer(直接内存)和FileChannel.transferTo()。通过调用操作系统底层的sendfile,数据可以在内核态直接从磁盘读入网卡,无需经过 CPU 拷贝到用户空间,极大提升了吞吐。
- 回答要点 :NIO 提供了
结语:IO 是高并发架构的底座
我深刻体会到:所有的业务代码最终都要跑在 IO 上。 你理解了 IO 模型,就理解了为什么数据库连接池要设得那么小,理解了为什么 Kafka 读写那么快,也理解了为什么在高并发场景下,线程池的参数不能乱设。
这篇文章是 IO 模型的最核心总结。如果你能结合这些知识点,在面试中自信地聊聊 Reactor 模型(Single Threaded vs Multi Threaded),那么架构师的 Offer 已经在向你招手了。