BIO、NIO、AIO 有什么区别?一文讲透 Java 三种 IO 模型
在 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 广泛。
五、三者对比
| 对比维度 | BIO | NIO | AIO |
|---|---|---|---|
| IO 模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
| 线程是否阻塞 | 等待和拷贝阶段都阻塞 | 等待阶段不阻塞,拷贝阶段由用户线程完成 | 两个阶段都不阻塞用户线程 |
| 编程复杂度 | 低 | 中高 | 高 |
| 典型实现 | Socket、ServerSocket | Channel、Buffer、Selector | AsynchronousSocketChannel |
| 适用场景 | 连接数少、短连接 | 高并发、长连接 | 连接多且 IO 耗时 |
| 代表框架 | 传统 Tomcat BIO 模式 | Netty、Tomcat NIO 模式 | 较少,部分中间件使用 |
六、如何选择
- 连接数少、逻辑简单:选 BIO,代码直观,维护成本低。
- 高并发、海量连接:选 NIO,配合 Netty 等框架能大幅降低开发难度。
- IO 操作耗时、追求极致线程利用率:可以考虑 AIO,但要评估复杂度和平台兼容性。
一句话总结:BIO 是「一直等」,NIO 是「先问再取」,AIO 是「交给你,好了通知我」。理解了这个本质,三者的区别就一目了然了。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)