在 Java 网络编程中,BIO、NIO、AIO 是绕不开的三个概念。它们代表了三种不同的 IO 模型,也对应着不同的并发处理思路。很多面试题会直接问「BIO、NIO、AIO 有什么区别」,但真正理解它们的差异,需要从操作系统层面的 IO 模型讲起。本文从概念、原理、代码示例和适用场景四个维度,把这三者讲清楚。

一、先理解 IO 的两个阶段

无论是哪种 IO 模型,一次完整的网络读取通常都包含两个阶段:

  • 等待数据就绪:数据从网络到达网卡,再被拷贝到内核缓冲区。这个阶段取决于网络和对端发送速度,耗时不确定。
  • 数据拷贝:数据从内核缓冲区拷贝到用户进程的缓冲区。这个阶段由 CPU 参与,相对较快。

BIO、NIO、AIO 的核心差异,本质上就是在这两个阶段中,用户线程是否被阻塞、由谁负责等待和通知。

二、BIO:同步阻塞 IO

BIO(Blocking IO)是最传统、最直观的 IO 模型。在 BIO 中,用户线程发起 read 调用后,会一直阻塞,直到数据就绪并完成拷贝才返回。

2.1 核心特点

  • 同步:用户线程需要自己等待 IO 操作完成。
  • 阻塞:在等待数据就绪和数据拷贝的两个阶段,线程都处于阻塞状态。
  • 一连接一线程:服务端通常为每个客户端连接分配一个独立线程处理。

2.2 代码示例

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("BIO 服务端启动,监听 8080");
        while (true) {
            // accept() 会阻塞,直到有客户端连接
            Socket socket = serverSocket.accept();
            // 每个连接交给一个线程处理
            new Thread(() -> handle(socket)).start();
        }
    }

    private static void handle(Socket socket) {
        try (InputStream in = socket.getInputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            // read() 会阻塞,直到有数据可读
            while ((len = in.read(buffer)) != -1) {
                System.out.println("收到:" + new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.3 优缺点

优点:模型简单,编程直观,适合连接数少、请求处理时间短的场景。

缺点:连接数一多,线程数量会急剧膨胀,线程上下文切换和内存开销成为瓶颈。虽然可以用线程池缓解,但本质上仍是「一连接一线程」,无法支撑海量连接。

三、NIO:同步非阻塞 IO

NIO(Non-blocking IO,Java 中也称 New IO)在 JDK 1.4 引入。它基于多路复用思想,通过 Selector 让一个线程管理多个 Channel。

3.1 核心组件

  • Channel:双向通道,可读可写,替代了 BIO 中的 Stream。
  • Buffer:数据容器,读写都通过 Buffer 完成。
  • Selector:多路复用器,一个线程可以监听多个 Channel 上的事件。

3.2 核心特点

  • 同步:数据拷贝阶段仍由用户线程完成,需要自己读取数据。
  • 非阻塞:发起 read 调用后立即返回,没有数据时不会阻塞线程。
  • 事件驱动:通过 Selector 轮询就绪事件,只处理真正有数据的连接。

3.3 代码示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        // 设置为非阻塞模式
        serverChannel.configureBlocking(false);
        // 注册接受连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO 服务端启动,监听 8080");

        while (true) {
            // 阻塞等待就绪事件,返回就绪的 Channel 数量
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    SocketChannel client = serverChannel.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = client.read(buffer);
                    if (len > 0) {
                        buffer.flip();
                        System.out.println("收到:" + new String(buffer.array(), 0, len));
                    } else if (len == -1) {
                        client.close();
                    }
                }
            }
        }
    }
}

3.4 优缺点

优点:单线程即可管理大量连接,资源占用低,适合高并发、连接数多的场景。Netty、Tomcat 的 NIO 模式都基于此。

缺点:编程模型复杂,需要处理半包、粘包、事件分发等问题,通常借助 Netty 等框架来简化开发。

四、AIO:异步非阻塞 IO

AIO(Asynchronous IO)在 JDK 1.7 引入,也叫 NIO 2.0。它的核心是异步回调:用户线程发起 IO 请求后立即返回,由操作系统完成数据就绪和数据拷贝,完成后通过回调通知用户线程。

4.1 核心特点

  • 异步:两个阶段都由操作系统完成,用户线程不参与等待和拷贝。
  • 非阻塞:发起请求后立即返回,不占用线程。
  • 回调驱动:通过 CompletionHandler 或 Future 获取结果。

4.2 代码示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AioServer {
    public static void main(String[] args) throws IOException {
        AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        System.out.println("AIO 服务端启动,监听 8080");

        // 异步接受连接,完成后回调
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Void attachment) {
                // 继续接受下一个连接
                serverChannel.accept(null, this);
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                // 异步读取,完成后回调
                client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer len, ByteBuffer buf) {
                        if (len > 0) {
                            buf.flip();
                            System.out.println("收到:" + new String(buf.array(), 0, len));
                        }
                    }

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

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

        // 防止主线程退出
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4.3 优缺点

优点:真正的异步,线程利用率最高,适合连接数多且 IO 操作耗时的场景。

缺点:编程模型最复杂,回调嵌套容易导致代码难以维护;在 Linux 上底层实现依赖 epoll 模拟,实际性能优势并不总是明显,因此生产中使用不如 NIO 广泛。

五、三者对比

对比维度BIONIOAIO
IO 模型同步阻塞同步非阻塞(多路复用)异步非阻塞
线程是否阻塞等待和拷贝阶段都阻塞等待阶段不阻塞,拷贝阶段由用户线程完成两个阶段都不阻塞用户线程
编程复杂度低中高高
典型实现Socket、ServerSocketChannel、Buffer、SelectorAsynchronousSocketChannel
适用场景连接数少、短连接高并发、长连接连接多且 IO 耗时
代表框架传统 Tomcat BIO 模式Netty、Tomcat NIO 模式较少,部分中间件使用

六、如何选择

  • 连接数少、逻辑简单:选 BIO,代码直观,维护成本低。
  • 高并发、海量连接:选 NIO,配合 Netty 等框架能大幅降低开发难度。
  • IO 操作耗时、追求极致线程利用率:可以考虑 AIO,但要评估复杂度和平台兼容性。

一句话总结:BIO 是「一直等」,NIO 是「先问再取」,AIO 是「交给你,好了通知我」。理解了这个本质,三者的区别就一目了然了。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐