我用一个餐馆点餐的比喻来帮你理解 IO(传统阻塞式IO) 和 NIO(非阻塞IO) 的区别:
1. 传统IO(Blocking IO)—— 单线程服务员
假设有一个餐馆,服务员(线程)每次只能服务一桌客人:
- 流程:客人点菜 → 服务员站在桌边等厨师做菜 → 菜做好后服务员端给客人 → 接着服务下一桌。
- 问题:服务员在等厨师做菜时(比如等待数据从硬盘/网络加载),完全被“卡住”(阻塞),无法服务其他客人。
- 缺点:客人多时,餐馆需要雇佣大量服务员(多线程),成本高(资源消耗大)。
2. NIO(Non-blocking IO)—— 多任务服务员
现在餐馆升级,服务员变得更高效:
-
流程:服务员不再傻等厨师,而是:
- 记录所有桌的需求(注册到 Selector)。
- 循环检查哪些桌的菜做好了(轮询),哪些桌需要新服务。
- 菜做好后,服务员立刻端过去(事件驱动)。
-
优势:一个服务员(单线程)就能同时处理多桌请求,资源利用率高。
-
核心概念:
- Channel(通道) :数据管道(如文件、网络连接)。
- Buffer(缓冲区) :临时存储数据的水池(读写通过缓冲区)。
- Selector(选择器) :事件监听器,告诉服务员哪些通道有数据可读/写。
3. 关键区别
特性 | 传统IO | NIO |
---|---|---|
阻塞方式 | 线程必须等待数据完成(阻塞) | 线程可以继续做其他事(非阻塞) |
处理模型 | 一个线程处理一个连接 | 一个线程管理多个连接(多路复用) |
适用场景 | 低并发、简单场景(如小文件读写) | 高并发、实时场景(如聊天服务器) |
4. 举个代码例子
-
传统IO:读取文件时,线程会卡在
read()
方法直到数据读完。java
复制
InputStream in = new FileInputStream("file.txt"); int data = in.read(); // 阻塞直到数据就绪
-
NIO:线程可以先做其他事情,数据就绪后再处理。
java
复制
SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); // 设置为非阻塞 selector.register(channel, SelectionKey.OP_READ); // 注册读事件 // 在循环中检查哪些通道有数据就绪 while (true) { int readyChannels = selector.select(); if (readyChannels == 0) continue; // 处理就绪的通道... }
5. 总结
- IO:简单直接,但性能低(适合小规模应用)。
- NIO:复杂但高效,适合高并发(如Netty框架底层就是NIO)。
就像餐馆优化服务流程一样,NIO通过“事件驱动”和“非阻塞”让程序更高效!