IO 模型
问题
Linux 的 5 种 IO 模型是什么?同步/异步、阻塞/非阻塞怎么区分?
答案
5 种 IO 模型
IO 操作分两个阶段:① 等待数据就绪 ② 将数据从内核拷贝到用户空间。
| 模型 | 等待数据 | 数据拷贝 | 说明 |
|---|---|---|---|
| 同步阻塞(BIO) | 阻塞 | 阻塞 | 两阶段都阻塞 |
| 同步非阻塞 | 非阻塞(轮询) | 阻塞 | 不断询问"好了没?" |
| IO 多路复用 | 阻塞在 select/epoll | 阻塞 | 一个线程监听多个 fd |
| 信号驱动 IO | 非阻塞(信号通知) | 阻塞 | 内核数据就绪后发信号 |
| 异步 IO(AIO) | 非阻塞 | 非阻塞 | 全程非阻塞,内核完成后回调 |
形象比喻
- BIO:你在餐厅排队等位,一直站着等(什么都干不了)
- NIO 轮询:你不断跑去问"有位子了吗?"
- IO 多路复用:你留了电话号叫号器响了通知你(一个叫号器管多桌)
- AIO:你点了外卖,送到家门口打电话叫你(过程全自动)
IO 多路复用
一个线程通过 select/poll/epoll 监听多个文件描述符(Socket),哪个就绪就处理哪个。这就是 Reactor 模式的底层原理。
同步/异步 vs 阻塞/非阻塞
| 概念 | 区分维度 | 说明 |
|---|---|---|
| 同步/异步 | 谁来完成 IO | 同步:用户线程自己读;异步:内核完成后通知用户 |
| 阻塞/非阻塞 | 等待时线程是否挂起 | 阻塞:线程挂起等待;非阻塞:立即返回 |
关键区分
- IO 多路复用是同步的(数据拷贝仍然由用户线程执行),但比 BIO 高效(一个线程管理多个连接)
- 真正的异步 IO(AIO)在 Linux 上还不够成熟,实际项目中用 IO 多路复用(epoll)+ Reactor 更多
常见面试问题
Q1: Java NIO 对应哪种 IO 模型?
答案:
Java NIO 使用 Selector,底层是 IO 多路复用(Linux 使用 epoll)。注意 Java 的 NIO 不是"非阻塞 IO"的意思,而是 New IO。
Selector 注册多个 Channel,调用 select() 阻塞等待就绪事件,有事件后逐个处理。详见 Socket 编程。
Q2: 为什么 Netty 不用 AIO?
答案:
- Linux 的 AIO 对网络 IO 支持不好,底层还是用 epoll 模拟
- epoll 已经足够高效(事件通知 O(1))
- Netty 基于 epoll 的 Reactor 模型已经非常成熟
AIO 在 Windows 上(IOCP)较完善,但 Java 服务端主要部署在 Linux。
Q3: Reactor 模式是什么?
答案:
Reactor 模式是基于 IO 多路复用的事件驱动模型:
| 模型 | 说明 |
|---|---|
| 单 Reactor 单线程 | 一个线程负责监听和处理(Redis 6.0 前) |
| 单 Reactor 多线程 | 一个线程监听,多线程处理 |
| 主从 Reactor | 主 Reactor 接收连接,从 Reactor 处理 IO(Netty) |