视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


一、前言:为什么 Java 要搞出三种 I/O?

很多初学者学完 FileInputStream 就以为“Java I/O 就这样了”,直到面试被问:“BIO 和 NIO 有什么区别?”才意识到——原来 Java 的 I/O 世界远比想象复杂!

今天我们就用 生活化比喻 + 代码实战 + 性能对比,一次性搞懂:

  • BIO(Blocking I/O):同步阻塞
  • NIO(Non-blocking I/O):同步非阻塞(注意:不是“异步”!)
  • AIO(Asynchronous I/O):真正异步(JDK 7 引入)

并告诉你:在 Spring Boot 项目中,到底该用哪种?


二、核心区别一句话总结

模型 全称 特点 线程模型 适用场景
BIO Blocking I/O 同步阻塞 1 连接 = 1 线程 低并发、简单应用
NIO Non-blocking I/O 同步非阻塞 1 线程 = 多连接(多路复用) 高并发、长连接
AIO Asynchronous I/O 异步非阻塞 事件回调(Proactor 模式) 超高并发、Linux 环境

🔥 关键理解:

  • 同步 vs 异步:是否需要主动“去拿结果”
  • 阻塞 vs 非阻塞:调用后是否“卡住当前线程”

三、逐个击破:用“餐厅点餐”比喻理解

🍽️ 场景:你在一家餐厅吃饭

✅ BIO(传统餐厅)
  • 你点完菜,服务员站在你桌边等厨房做好(线程阻塞)。
  • 厨房没好,他就不能服务别人。
  • 100 个客人 → 需要 100 个服务员(线程爆炸!)
✅ NIO(快餐店 + 叫号系统)
  • 你点完菜,服务员给你一个号牌,继续服务下一位
  • 厨房做好后,叫号器通知(Selector 轮询)。
  • 你听到叫号,自己去取餐(线程主动 read)。
  • 1 个服务员 + 1 个叫号器 → 服务 1000 人
✅ AIO(智能送餐机器人)
  • 你点完菜,直接回座位玩手机
  • 厨房做好后,机器人自动把菜送到你桌上(回调通知)。
  • 你完全不用关心“什么时候好”、“要不要去拿”。
  • 真正“无感”体验

四、代码实战对比(Spring Boot 环境)

⚠️ 注意:以下示例仅用于教学,生产环境请优先考虑 Netty(基于 NIO)


1️⃣ BIO 示例:传统 Socket(每连接一线程)

// BioServer.java
public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        System.out.println("BIO 服务器启动,端口 8080");

        while (true) {
            Socket client = server.accept(); // ⚠️ 阻塞!
            // 每来一个连接,开一个新线程
            new Thread(() -> {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                     PrintWriter out = new PrintWriter(client.getOutputStream(), true)) {
                    String msg;
                    while ((msg = in.readLine()) != null) {
                        System.out.println("收到: " + msg);
                        out.println("Echo: " + msg);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

问题

  • 连接数 > 线程数上限 → OOM
  • 空闲连接也占用线程资源

2️⃣ NIO 示例:单线程处理多连接

// NioServer.java
public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(8080));
        server.configureBlocking(false);
        server.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select(); // 阻塞等待事件
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();

            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove();

                if (key.isAcceptable()) {
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = client.read(buf); // ⚠️ 非阻塞:可能返回 0
                    if (len > 0) {
                        buf.flip();
                        String msg = new String(buf.array(), 0, len);
                        System.out.println("收到: " + msg);
                        client.write(ByteBuffer.wrap(("Echo: " + msg).getBytes()));
                    } else if (len == -1) {
                        client.close();
                    }
                }
            }
        }
    }
}

优势

  • 单线程可处理上万连接
  • 资源占用低

缺点

  • 编程复杂
  • 仍需轮询(select()

3️⃣ AIO 示例:真正的异步回调(JDK 7+)

// AioServer.java
public class AioServer implements CompletionHandler<Integer, ByteBuffer> {
    private AsynchronousServerSocketChannel server;

    public AioServer(int port) throws IOException {
        server = AsynchronousServerSocketChannel.open();
        server.bind(new InetSocketAddress(port));
        System.out.println("AIO 服务器启动,端口 " + port);
    }

    public void start() {
        // 接受连接(异步)
        server.accept(this, new CompletionHandler<AsynchronousSocketChannel, AioServer>() {
            @Override
            public void completed(AsynchronousSocketChannel client, AioServer attachment) {
                // 继续接受下一个连接
                server.accept(attachment, this);
                // 读数据(异步)
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer buf) {
                        if (result > 0) {
                            buf.flip();
                            String msg = new String(buf.array(), 0, result);
                            System.out.println("收到: " + msg);
                            // 回写(也是异步)
                            client.write(ByteBuffer.wrap(("Echo: " + msg).getBytes()), null, null);
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer buf) {
                        exc.printStackTrace();
                    }
                });
            }

            @Override
            public void failed(Throwable exc, AioServer attachment) {
                exc.printStackTrace();
            }
        });
    }

    public static void main(String[] args) throws IOException {
        new AioServer(8081).start();
        // 主线程不阻塞!
        try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) {}
    }
}

优势

  • 真正异步,无需轮询
  • 系统级通知(Linux epoll / Windows IOCP)

现实问题

  • Linux 下 AIO 支持差(JDK AIO 实际是用线程池模拟的!)
  • 编程极其复杂(回调地狱)
  • Netty 官方明确表示:不推荐使用 AIO

五、反例 & 注意事项 ⚠️

❌ 反例1:在 NIO 中做耗时操作(阻塞 Selector 线程)

if (key.isReadable()) {
    // 读数据...
    saveToDB(msg); // ⚠️ 阻塞整个事件循环!
}

✅ 正确做法:提交到业务线程池


❌ 反例2:AIO 在 Linux 上性能不如 NIO

JDK 的 AIO 在 Linux 上底层仍是 epoll + 线程池模拟,并非真正的内核 AIO(io_uring)。
所以 Netty、Tomcat、Redis 等主流框架都选择 NIO(Reactor 模式)


❌ 反例3:BIO 用于高并发场景

10000 个连接 → 10000 个线程 → 内存爆炸(每个线程约 1MB 栈空间)!


六、Spring Boot 中如何选择?

场景 推荐方案
Web 应用(HTTP) Spring Web(基于 Tomcat,内部用 NIO)
自定义 TCP/UDP 协议 Netty(NIO 封装)
文件读写 使用 Files 工具类 或 FileChannel(NIO)
超高性能网关 Netty + Epoll(Linux 优化)

📌 结论:99% 的场景,用 Netty(NIO)就够了!AIO 暂不成熟,BIO 仅用于学习。


七、总结对比表

特性 BIO NIO AIO
模型 同步阻塞 同步非阻塞 异步非阻塞
线程模型 1 连接 : 1 线程 1 线程 : 多连接 回调驱动
编程难度 简单 复杂 极其复杂
性能 理论最高(但实际受限)
成熟度 高(主流) 低(Linux 支持差)
典型框架 Tomcat(早期) Netty、Tomcat(NIO 模式) 无主流应用

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐