Java BIO / NIO / AIO 完整详解 + 编程技巧

Java BIO / NIO / AIO 完整详解 + 编程技巧

  • BIO:同步阻塞
  • NIO:同步非阻塞(多路复用)
  • AIO:异步非阻塞(NIO.2)

一、BIO 阻塞 IO

原理

BIO = 一连接一线程。accept()、read()、write()全是阻塞调用。 没有客户端连接,accept()卡住;连接建立后没有数据,read()卡住线程。

简易代码

复制代码
public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("BIO服务启动");
        while (true) {
            // 阻塞:没有连接就卡住
            Socket socket = serverSocket.accept();
            // 每个连接新开一个线程
            new Thread(() -> {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                    String line;
                    while ((line = br.readLine()) != null) {
                        System.out.println("收到客户端:" + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

✅ BIO 编程技巧

  1. 不要直接无限制创建线程:生产要用线程池版本 BIO,限制最大连接,防止线程打爆 OS。

    ExecutorService pool = Executors.newFixedThreadPool(10);
    // 收到socket后 pool.submit(任务);

  2. 必须关闭流和 Socket,使用 try-with-resources 自动释放资源,避免文件句柄泄漏。

  3. BIO 适合短连接、并发量很小场景;连接数上百就会出现大量线程上下文切换、内存飙升。

  4. 问题:连接空闲的时候线程也被占用,线程资源浪费严重。

❌ BIO 痛点

  • 连接越多,线程越多,操作系统线程上限有限。
  • 线程阻塞,无法复用。

二、NIO(Java NIO,NIO.1)同步非阻塞,多路复用【重点】

三大核心组件

  1. Channel 通道 :双向读写,类似 Socket,SocketChannel / ServerSocketChannel / FileChannel
  2. Buffer 缓冲区 :数据读写必须经过 Buffer。ByteBuffer最常用。
  3. Selector 选择器 :多路复用器,一个线程监听多个 Channel 事件。

事件类型:OP_ACCEPT(连接就绪)、OP_READ(读就绪)、OP_WRITE(写就绪)
关键:NIO 只是等待数据阶段非阻塞 ,数据从内核拷贝到用户缓冲区,仍然是应用线程自己完成(同步)

NIO 服务端最简示例

复制代码
public class NioServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8888));
        // 设置通道非阻塞!!NIO核心开关
        serverChannel.configureBlocking(false);

        Selector selector = Selector.open();
        // 注册ACCEPT事件到selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞,等待就绪事件(可以带超时 selector.select(1000))
            selector.select();
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove(); // 必须手动移除,否则重复处理
                if (key.isAcceptable()) {
                    // 接受连接
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = server.accept();
                    clientChannel.configureBlocking(false);
                    // 注册读事件
                    clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buf = (ByteBuffer) key.attachment();
                    buf.clear();
                    int read = channel.read(buf);
                    if (read > 0) {
                        buf.flip(); // flip:切换读写模式!NIO高频易错点
                        byte[] data = new byte[buf.remaining()];
                        buf.get(data);
                        System.out.println("NIO收到:" + new String(data));
                    } else if (read == -1) {
                        // 客户端关闭连接
                        key.cancel();
                        channel.close();
                    }
                }
            }
        }
    }
}

✅ NIO 编程技巧(高频踩坑点)

  1. configureBlocking(false) 必须设置,否则仍然阻塞。
  2. flip() 翻转缓冲区 :
    • write模式:position 写,limit=capacity
    • flip:limit=position,position=0,切换到读模式。
    • clear() 不是清空数据,只是重置 position/limit。
  3. selectedKeys集合必须迭代时 remove,事件处理完不删除,会重复触发旧事件,死循环。
  4. JDK1.6 及之前 Selector 存在空轮询 bug :select()返回 0,CPU100%。Netty 已经做了修复。
  5. OP_WRITE 事件不要随便注册:写事件频繁就绪,会疯狂触发,一般只有缓冲区满的时候才注册 OP_WRITE。
  6. ByteBuffer 是固定容量,半包 / 粘包需要自己处理协议(Netty 封装)。
  7. 单 Selector 单线程模型适合 IO 密集;高并发可以用主从 Selector(Reactor):一个 selector 专门接受连接,多个 selector 处理读写(Netty 默认模型)。

❌ NIO 痛点

原生 NIO API 极其繁琐,半包粘包、异常处理、断线检测全部手写,生产一般直接用 Netty 封装。


三、AIO NIO.2(异步非阻塞 IO)

原理

发起 IO 请求后,线程立刻返回,操作系统内核完成【等待数据 + 内核拷贝到应用缓冲区】 ,全部做完后回调CompletionHandler。 Java 核心类:AsynchronousServerSocketChannel、AsynchronousSocketChannel

AIO 服务端简单示例

复制代码
public class AioServer {
    public static void main(String[] args) throws IOException {
        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(8888));
        // 注册接受连接回调
        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Void attachment) {
                // 继续接收下一个连接
                server.accept(null, this);
                ByteBuffer buf = ByteBuffer.allocate(1024);
                // 发起异步读,读完自动回调
                client.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer buffer) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        System.out.println("AIO收到:" + new String(data));
                    }
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        exc.printStackTrace();
                    }
                });
            }
            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });
        // 主线程阻塞等待,防止程序退出
        LockSupport.park();
    }
}

✅ AIO 编程技巧

  1. AIO 依赖操作系统异步 IO 支持,Linux 下 AIO 底层实现弱,Windows IOCP 很强,所以 Java 服务端很少用。
  2. 回调是嵌套式,业务复杂后回调地狱,可读性差。
  3. 回调线程由系统线程池提供,注意线程安全。
  4. 连接关闭、异常要捕获,否则容易句柄泄漏。

❌ AIO 痛点

Linux 下对网络 AIO 支持不完善,Netty5 曾经尝试 AIO,后来废弃。业务开发基本不用 AIO。


四、三大模型对比表

表格

模型 IO 类型 线程模型 优点 缺点 适用场景
BIO 同步阻塞 1 连接 1 线程 代码最简单 连接多线程爆炸 低并发、少量连接
NIO 同步非阻塞,多路复用 少量线程管理大量连接 连接数高,资源省 API 复杂,半包粘包需要自己处理 高并发长连接:IM、网关、RPC(Netty)
AIO 异步非阻塞 内核回调 线程利用率最高 Linux 支持差,回调嵌套复杂 Windows 服务器,Java 后端极少使用
相关推荐
霸道流氓气质1 小时前
OpenTelemetry 入门与实战:Java Agent、Spring Boot Starter与LLM调用追踪示例
java·开发语言·spring boot
波力海苔夹心脆6751 小时前
C# 机器视觉实战:单相机引导机械手精准贴合,从拍照、偏差计算(平移+旋转)到坐标补偿全解
开发语言·经验分享·数码相机·c#·视觉检测·.net
m0_587383001 小时前
24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·spring·系统架构·需求分析
栖凤1 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
小师兄吃牛肉1 小时前
Java 同步系列之 ZooKeeper 分布式锁
java·分布式·java-zookeeper
外收内放1 小时前
Python基础语法练习题(62原始版本及其优化版本)
开发语言·python
马剑威(威哥爱编程)2 小时前
【AI全栈后端12-12】Spring Boot 3.x 到 4.x 迁移实操:Jakarta 11、Jackson 3 与 AI 2.0
java·开发语言·spring boot·后端
Gauss松鼠会2 小时前
【GaussDB】破除gaussdb ugin索引支持中文模糊查询的迷思-字符序
java·运维·服务器·网络·数据库·gaussdb·经验总结
IT研究室2 小时前
最新计算机毕业设计选题推荐-基于spring boot的心桥·心理健康综合服务平台-网站-文档指导-Java-springboot
java·spring boot·课程设计