IO模型有哪几种
Java / 后端开发中核心的 IO 模型分类,是网络编程、高并发开发的基础知识点。主流的 IO 模型分为5 类(从最基础的阻塞 IO 到高性能的异步 IO),本文用通俗易懂的语言从浅到深讲解,结合场景和比喻快速理解。
一、IO 模型的核心分类(按 UNIX/POSIX 标准定义)
先明确核心概念:IO 操作的本质是「数据从内核缓冲区到用户缓冲区」的拷贝过程,IO 模型的差异体现在等待数据阶段和拷贝数据阶段的阻塞 / 非阻塞特性。
1. 阻塞 IO(Blocking IO,BIO)
- 通俗比喻:你去奶茶店点单,点完后站在柜台前一直等,直到拿到奶茶(期间不能做任何事)。
- 核心逻辑:
- 应用程序发起 IO 请求(如读取网络数据)后,全程阻塞:
① 等待内核准备好数据(比如网卡接收到数据并写入内核缓冲区);
② 内核将数据拷贝到用户缓冲区。 - 直到整个过程完成,线程才解除阻塞。
- 应用程序发起 IO 请求(如读取网络数据)后,全程阻塞:
- 代码示例(Java BIO):
java
import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; // BIO服务端:一个连接对应一个线程,线程全程阻塞等待数据 public class BIOExample { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("BIO服务端启动,等待连接..."); while (true) { // 阻塞:等待客户端连接 Socket socket = serverSocket.accept(); // 新建线程处理该连接(否则无法接收下一个连接) new Thread(() -> { try { InputStream in = socket.getInputStream(); byte[] buf = new byte[1024]; // 阻塞:等待数据并拷贝到用户缓冲区 int len = in.read(buf); if (len > 0) { System.out.println("收到数据:" + new String(buf, 0, len)); } in.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } }).start(); } } } - 特点:简单易实现,但线程资源消耗极大(高并发下会创建大量线程),性能差。
- 适用场景:连接数少、并发低的场景(如简单的本地文件读取)。
2. 非阻塞 IO(Non-Blocking IO,NIO)
- 通俗比喻:你点完奶茶后,不站在柜台等,而是每隔 1 分钟去问一次 “我的奶茶好了吗?”(轮询),期间可以刷手机、聊天。
- 核心逻辑:
- 应用程序发起 IO 请求后,立即返回(不阻塞):
① 若内核数据未准备好,返回「未就绪」状态,应用程序需要不断轮询检查;
② 一旦数据准备好,内核拷贝数据到用户缓冲区(此阶段仍阻塞)。
- 应用程序发起 IO 请求后,立即返回(不阻塞):
- 特点:线程无需一直等待,但轮询会消耗 CPU 资源,效率依然不高。
- 适用场景:连接数较少、需要快速响应的简单场景(极少单独使用)。
3. IO 多路复用(IO Multiplexing,也叫多路转接)
- 通俗比喻:你点完奶茶后,告诉店员 “好了叫我”,然后坐在座位上;同时还有 10 个人也点了奶茶,店员统一管理所有订单,做好后逐个喊人取(一个店员管理多个订单)。
- 核心逻辑:
- 引入「IO 多路复用器」(如 Linux 的
select/poll/epoll、Java 的Selector),一个线程可以监听多个 IO 通道:
① 应用程序将所有待监听的 IO 通道注册到复用器上,线程阻塞在复用器上(而非单个 IO 通道);
② 当任意 IO 通道的数据准备好,复用器会通知线程,线程再去处理对应的 IO(拷贝数据阶段仍阻塞)。
- 引入「IO 多路复用器」(如 Linux 的
- Java NIO 就是基于此模型,代码示例(核心片段):
java
import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; // NIO多路复用核心逻辑 public class NIOSelectorExample { public static void main(String[] args) throws Exception { // 1. 创建多路复用器(Selector) Selector selector = Selector.open(); // 2. 创建服务端通道并注册到Selector ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.bind(new java.net.InetSocketAddress(8080)); serverChannel.configureBlocking(false); // 设置为非阻塞 serverChannel.register(selector, java.nio.channels.SelectionKey.OP_ACCEPT); while (true) { // 3. 阻塞:等待有就绪的IO事件(连接/数据可读) selector.select(); // 4. 遍历所有就绪的事件并处理 for (java.nio.channels.SelectionKey key : selector.selectedKeys()) { if (key.isAcceptable()) { // 处理新连接 SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); // 注册读事件 clientChannel.register(selector, java.nio.channels.SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理数据读取(拷贝阶段仍阻塞,但仅针对就绪的通道) SocketChannel clientChannel = (SocketChannel) key.channel(); java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(1024); int len = clientChannel.read(buf); if (len > 0) { System.out.println("收到数据:" + new String(buf.array(), 0, len)); } clientChannel.close(); } } selector.selectedKeys().clear(); // 清空已处理的事件 } } } - 特点:一个线程管理多个 IO 通道,CPU 利用率高,是高并发网络编程的基础(如 Nginx、Netty 底层)。
- 关键区别:
select/poll(轮询所有注册通道)效率低于epoll(事件通知机制),Java NIO 在 Linux 下默认用 epoll。
4. 信号驱动 IO(Signal Driven IO,SIGIO)
- 通俗比喻:你点完奶茶后留了手机号,店员做好后给你发个短信,你收到短信后去柜台取奶茶。
- 核心逻辑:
- 应用程序通过「信号机制」注册 IO 事件,然后线程继续执行其他任务;
- 内核数据准备好后,向应用程序发送
SIGIO信号,应用程序收到信号后,再发起 IO 调用(拷贝数据阶段阻塞)。
- 特点:无需轮询,但信号处理逻辑复杂,Java 中几乎不直接使用,仅作为底层理论模型。
5. 异步 IO(Asynchronous IO,AIO)
- 通俗比喻:你点完奶茶后直接回家,店员做好后亲自把奶茶送到你家(全程无需你参与等待 / 取货)。
- 核心逻辑:
- 应用程序发起 IO 请求后,立即返回,内核会完成「等待数据 + 拷贝数据」的全流程;
- 当整个 IO 过程(数据拷贝到用户缓冲区)完成后,内核通过回调 / 通知的方式告知应用程序。
- Java AIO(NIO.2)示例:
java
import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.net.InetSocketAddress; import java.nio.ByteBuffer; // AIO服务端:异步处理连接和数据读取 public class AIOExample { public static void main(String[] args) throws Exception { // 1. 创建异步服务端通道 AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(); serverChannel.bind(new InetSocketAddress(8080)); System.out.println("AIO服务端启动,等待连接..."); // 2. 异步接受连接(非阻塞,完成后回调) serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel clientChannel, Object attachment) { // 继续接受下一个连接(否则只能处理一个连接) serverChannel.accept(null, this); // 3. 异步读取数据(完成后回调) ByteBuffer buf = ByteBuffer.allocate(1024); clientChannel.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer len, ByteBuffer buffer) { if (len > 0) { buffer.flip(); System.out.println("收到数据:" + new String(buffer.array(), 0, len)); } try { clientChannel.close(); } catch (Exception e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); } }); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); // 主线程不退出 Thread.sleep(Integer.MAX_VALUE); } } - 特点:全异步、无阻塞,性能最高,但实现复杂,需要操作系统底层支持(Linux 下 AIO 支持有限)。
- 适用场景:高并发、大文件传输、低延迟的场景(如分布式存储、高性能中间件)。
二、核心对比表(快速记忆)
| IO 模型 | 等待数据阶段 | 拷贝数据阶段 | 核心特点 | Java 实现 |
|---|---|---|---|---|
| 阻塞 IO(BIO) | 阻塞 | 阻塞 | 简单但性能差 | Socket、ServerSocket |
| 非阻塞 IO(NIO) | 非阻塞(轮询) | 阻塞 | 减少等待,但 CPU 开销大 | Java NIO(非 Selector) |
| IO 多路复用 | 阻塞(复用器) | 阻塞 | 一个线程管理多通道,高效 | Java NIO Selector |
| 信号驱动 IO | 非阻塞(信号) | 阻塞 | 无需轮询,实现复杂 | 无直接 Java API |
| 异步 IO(AIO) | 非阻塞 | 非阻塞 | 全异步,性能最高 | Java NIO.2(AIO) |
总结
- 基础核心:IO 模型的核心差异在于「等待数据」和「拷贝数据」两个阶段是否阻塞;
- 实战重点:日常开发中,BIO 用于简单场景,NIO(多路复用)是高并发主流(如 Netty),AIO 适用于超高性能需求场景;
- Java 视角:Java 中的 NIO≠非阻塞 IO,而是「IO 多路复用」,AIO 才是真正的异步 IO,且 Netty 虽基于 NIO,但通过封装实现了接近 AIO 的异步体验。
*
备注:公众号清汤袭人能找到我,那是随笔的地方
备注:公众号清汤袭人能找到我,那是随笔的地方

浙公网安备 33010602011771号