IO(传统阻塞式IO)和 NIO(非阻塞IO)

27 阅读2分钟

我用一个餐馆点餐的比喻来帮你理解 IO(传统阻塞式IO)  和 NIO(非阻塞IO)  的区别:


1. 传统IO(Blocking IO)—— 单线程服务员

假设有一个餐馆,服务员(线程)每次只能服务一桌客人:

  • 流程:客人点菜 → 服务员站在桌边等厨师做菜 → 菜做好后服务员端给客人 → 接着服务下一桌。
  • 问题:服务员在等厨师做菜时(比如等待数据从硬盘/网络加载),完全被“卡住”(阻塞),无法服务其他客人。
  • 缺点:客人多时,餐馆需要雇佣大量服务员(多线程),成本高(资源消耗大)。

2. NIO(Non-blocking IO)—— 多任务服务员

现在餐馆升级,服务员变得更高效:

  • 流程:服务员不再傻等厨师,而是:

    1. 记录所有桌的需求(注册到 Selector)。
    2. 循环检查哪些桌的菜做好了(轮询),哪些桌需要新服务。
    3. 菜做好后,服务员立刻端过去(事件驱动)。
  • 优势:一个服务员(单线程)就能同时处理多桌请求,资源利用率高。

  • 核心概念

    • Channel(通道) :数据管道(如文件、网络连接)。
    • Buffer(缓冲区) :临时存储数据的水池(读写通过缓冲区)。
    • Selector(选择器) :事件监听器,告诉服务员哪些通道有数据可读/写。

3. 关键区别

特性传统IONIO
阻塞方式线程必须等待数据完成(阻塞)线程可以继续做其他事(非阻塞)
处理模型一个线程处理一个连接一个线程管理多个连接(多路复用)
适用场景低并发、简单场景(如小文件读写)高并发、实时场景(如聊天服务器)

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通过“事件驱动”和“非阻塞”让程序更高效!