对于 Java 开发者来说,理解这三种 IO 模型是进阶架构师的必经之路。我们可以把 内核(Kernel) 想象成“快递公司”,把 用户线程 想象成“收件人”,来看看这三种模型的区别:
1. 阻塞 IO (Blocking IO, BIO) —— “死等快递”
这是最传统的 IO 模型。
-
场景描述:你下单买了个东西(发起
read请求),然后就坐在门口死死盯着路口(线程阻塞)。在快递员把货送到你手上之前,你什么都干不了,连手机都不玩(交出 CPU)。 -
内核行为:内核查看数据没就绪,就让线程睡觉(Block)。数据就绪后,内核把数据拷贝到用户内存,再叫醒线程。
-
缺点:一个连接对应一个线程。如果 1000 个人同时等快递,就需要 1000 个线程,系统开销极大。
2. 非阻塞 IO (Non-blocking IO, NIO) —— “狂打电话查进度”
Java NIO(New IO)的核心实现。
-
场景描述:你下单后,回房间打游戏了。但你每隔 5 分钟就跑出来问快递员:“货到了吗?”。快递员说“没到”,你就回去接着打。
-
内核行为:用户线程发起
read,如果数据没好,内核立即返回一个 Error。线程不会阻塞,但需要不断轮询(Polling)。 -
特点:线程不被挂起,但轮询会极大地占用 CPU。
-
进阶(IO 多路复用):为了不盲目轮询,引入了“传达室大爷”(Selector/epoll)。1000 个人的快递都由大爷盯着,谁的到了大爷才喊谁。这就是 Netty 底层的逻辑。
3. 异步 IO (Asynchronous IO, AIO) —— “快递放门口,签收完发短信”
这是最理想的 IO 模型(Java 7 引入的 NIO 2.0)。
-
场景描述:你下单后,直接去睡觉或忙别的了,完全不关心快递。快递公司(内核)负责把货送到你门口,甚至帮你搬进屋(拷贝数据),全部搞定后给你发个短信:“货已入库,请享用”。
-
内核行为:用户线程发起
read后立即去干别的。内核负责等待数据、拷贝数据。整个过程完成后,内核会通过回调(Callback)通知用户线程。 -
特点:真正的异步。线程完全不需要关心 IO 过程,只需处理结果。
总结对比表
💡 面试官可能会问的“资深”问题:
-
既然 AIO 这么好,为什么 Netty 还用 NIO?
-
回答要点:在 Linux 系统上,AIO 的底层实现不如 NIO (epoll) 成熟。NIO 配合 Reactor 模式已经能压榨出极高的性能,且兼容性更好。
-
-
结合你的思维导图:
-
在 BIO 中,你会看到大量的线程处于
BLOCKED或WAITING状态。 -
在 NIO 中,线程更多处于
RUNNABLE状态,因为它们在Selector上循环。 -
利用
jstack分析时,如果发现nioEventLoop线程压力过大,通常是逻辑处理太重,需要优化。
-
需要我帮你用 Java 代码展示一下这三种模式在 Socket 编程中的具体写法差异吗? 这能帮你更深刻地理解“回调”和“轮询”的区别。