news 2026/7/12 10:53:59

网络IO编程:AIO、BIO、NIO

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
网络IO编程:AIO、BIO、NIO

在前面的章节中,我们聚焦于 Java 并发的核心——线程之间的协作与控制

锁、CAS、AQS、CountDownLatch、Semaphore……

这些工具让我们能优雅地协调多个线程,避免数据错乱与资源争抢。

真正的性能瓶颈,往往不在 CPU 内部的微秒级竞争,而在线程与外部世界交互时的毫秒级等待—— I/O。

当线程发起一次数据库查询、读取一个网络数据包、或写入一个文件时,它可能要“空等”几十甚至几百毫秒。

在这段时间里,线程被阻塞,无法执行其他任务,而系统却不得不为它保留一份宝贵的栈内存和调度开销。

这就是“并发真正的战场”:不是线程之间抢资源,而是如何让线程在 I/O 等待时不再“睡大觉”。

接下来,我们将正式进入 I/O 编程的世界,从最传统的BIO(阻塞 I/O)开始,逐步揭开NIO(非阻塞 I/O + 多路复用)的神秘面纱,并最终抵达AIO(异步 I/O)的前沿。我们还会深入探讨Reactor/Proactor这两种经典的事件驱动架构,理解它们如何支撑起高性能服务器(如 Netty、Redis、Nginx)。

同步与异步IO

同步IO与异步IO,这是理解所有IO模型的基石。

两者的区别关键在于发起IO请求后,线程是否需要等待IO操作真正完成

同步IO是你发起了IO请求后,阻塞等待,直到数据完成。

异步IO是你发起了IO请求后,立刻返回,IO完成后再主动通知你。

1、同步IO

同步IO又可以分为阻塞IO非阻塞IO,区别在于等待的方式不同。

1.1、阻塞IO(BIO)

这是最传统的IO模型。

InputStream in = socket.getInputStream(); byte[] buf = new byte[1024]; in.read(buf); // 线程在这里阻塞,直到数据完全到达 // 数据到手,继续执行

特点

  • 线程发起read()调用后,一直等待,直到数据从内核拷贝到用户空间。
  • 在此期间线程什么都不做,被操作系统挂起。

类比:你去奶茶店排队,站在柜台前一动不动,直到奶茶做好递到你手上。

1.2、非阻塞IO(NIO)

线程发起 IO 请求后,不等待,立即返回一个状态(“数据还没准备好”或“数据准备好了”)。

SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); // 设置为非阻塞模式 ByteBuffer buf = ByteBuffer.allocate(1024); int bytesRead = channel.read(buf); // 立即返回 // bytesRead = 0 表示还没数据 // bytesRead > 0 表示读到了数据

特点

  • 每次read()调用都立即返回,不会阻塞。
  • 但如果数据没准备好,返回 0,线程需要反复轮询(自旋)检查数据是否就绪。

类比:你去奶茶店,问“好了吗?”,店员说“还没”,你就走了,过一会儿再来问一次,直到拿到奶茶。

问题:轮询会浪费 CPU,所以通常需要配合IO 多路复用(Selector)来使用。

那这么看来,非阻塞IO和异步IO不是一样了吗?

这里讲一下IO重要的两个过程:

  • 数据就绪:将信息读取到内核/进程缓冲区,准备发送
  • 数据拷贝:发生数据

以Java和MySql为例

输出IO:
当你在 Java 里执行一条 SQL 语句时,Java 程序需要把这条指令发送给 MySQL。

Java应用层 → 内核Socket发送缓冲区 → 网卡 → 网络线路 → MySQL 服务器的网卡 → MySQL内核接收缓冲区

输入IO

MySQL 处理完你的订单后,把查询结果返回给 Java。

MySQL内核发送缓冲区 → 网卡 → 网络线路 → Java程序的网卡 → 内核Socket接收缓冲区 → Java应用层

所以他们的区别是:

IO 模型

第一阶段:数据就绪

第二阶段:数据拷贝

非阻塞 IO

不阻塞(立即返回,但需轮询)

阻塞(线程自己把数据从内核拷贝到用户空间)

异步 IO

不阻塞(立即返回)

不阻塞(系统帮你拷贝完,然后通知你)

关键点:非阻塞 IO 在数据拷贝阶段仍然是阻塞的,而异步 IO 全程不阻塞。

做一个生活例子:

非阻塞 IO

你点了一杯奶茶,每隔一分钟就去问店员:“好了吗?”

  • 店员说“还没”(数据未就绪)→ 你走了,过一会儿再来问
  • 店员说“好了”(数据就绪)→你站在柜台前等店员把奶茶做好递到你手上(数据拷贝阶段,你在等)

你在“轮询”时不阻塞,但在“拿到奶茶”的那一刻,你还是得等着。

异步 IO

你在 App 上点奶茶,然后去打游戏。

  • 奶茶做好了,骑手直接送到你家门口,按门铃通知你(系统主动回调)
  • 你开门拿奶茶就行

你全程没有等过任何人,从制作到送达,都是系统在帮你搞定。

2、异步IO(AIO)

JDK7中引入了AIO(Asynchronous IO)

特点

  • 线程发起read()调用后,立即返回,完全不用等待。
  • 当 IO 操作真正完成时,系统会主动回调你注册的处理函数。
  • 线程在整个过程中从未阻塞,可以做其他任务。

类比

你在 App 上点奶茶,然后去打游戏、看电影。奶茶做好了,骑手打电话通知你来取。你全程没浪费时间等待。

3、架构设计模式

Reactor和Proactor

3.1、Reactor(反应器模式)

核心思想“来了事件我通知你,你来处理”

工作机制

  1. 服务员(Reactor)站在前台,负责监听所有的客人(Socket 连接)。
  2. 当有客人按门铃(比如数据可读了),服务员立刻通知后厨的厨师(Handler)。
  3. 厨师必须亲自走过去,把菜端回来(应用程序主动调用read()读取数据),然后再开始炒菜(执行业务逻辑)。

特点:感知的是“就绪事件”(告诉你“可以读了”),但实际的 I/O 读写操作还是由应用程序自己完成

在 Java 中NIO 就是典型的 Reactor 模式。它通过Selector(多路复用器)来监听事件,然后在事件到来时,由工作线程去执行channel.read()

组件

角色

职责

Event(事件)

信号

表示“有数据可读”或“可以写数据”

Reactor(反应器)

调度员

循环等待事件,事件来了分发给对应的 Handler

Demultiplexer(多路分发器)

监听器

底层调用select()/epoll(),阻塞等待事件就绪

Dispatcher(分发器)

派单员

将就绪的事件分配给对应的 Handler

Handler(处理器)

工人

实际执行读写操作和业务逻辑

Step 1: Reactor 启动,让 Demultiplexer 开始监听所有 Socket Step 2: Demultiplexer 调用 select(),阻塞等待 Step 3: 有数据到达,select() 返回 Step 4: Dispatcher 拿到就绪的事件列表 Step 5: Dispatcher 把事件分配给对应的 Handler Step 6: Handler 自己调用 channel.read() 读取数据 Step 7: Handler 处理业务逻辑 Step 8: 回到 Step 2,继续循环

代码解释:

// Reactor = 调度员 Selector selector = Selector.open(); // Demultiplexer = select() 阻塞等待 while (true) { selector.select(); // 阻塞,直到有事件就绪 // Dispatcher = 遍历就绪的事件 Set<SelectionKey> keys = selector.selectedKeys(); for (SelectionKey key : keys) { // Handler = 处理事件 if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buf = ByteBuffer.allocate(1024); channel.read(buf); // Handler 自己读数据 // 业务处理... } } }

3.2、Proactor(前摄器模式)

核心思想“你告诉我想吃什么,我做完了再叫你”

工作机制

  1. 客人(应用程序)把菜单和空盘子(缓冲区)一起交给服务员(Proactor)。
  2. 服务员把菜单交给后厨(操作系统内核),自己就不用管了
  3. 后厨做完菜(内核完成数据拷贝),直接端到客人桌上,然后喊一声“饭好了”(回调通知)。

特点:感知的是“完成事件”(告诉你“已经读完了”),I/O 操作(数据拷贝)完全由操作系统内核完成,应用程序只负责处理结果。

在 Java 中AIO 就是 Proactor 模式。你调用AsynchronousSocketChannel.read(buffer, attachment, handler),传入缓冲区和处理回调,线程立即返回,内核读完后自动回调给你。

组件

角色

职责

Operation(操作)

任务单

告诉系统“我要读/写,结果放这里”

Proactor(前摄器)

调度员

发起异步操作,等待完成通知

Completion Handler(完成处理器)

回调

系统完成 IO 后,自动调用的函数

Asynchronous Operation Processor(异步操作处理器)

执行者

操作系统内核,真正执行 IO 操作

Completion Dispatcher(完成分发器)

派单员

将完成的事件分发给对应的 Handler

Step 1: 你告诉 Proactor:“我要读这个 Socket 的数据,读完了放到 buffer 里,然后调用我的回调函数” Step 2: Proactor 把任务交给操作系统内核 Step 3: 内核开始读数据,你把这件事忘了,去做别的事 Step 4: 内核读完了,数据已经放到了你给的 buffer 里 Step 5: 内核通知 Proactor:“读完了” Step 6: Completion Dispatcher 找到你注册的回调函数 Step 7: 回调函数被调用,你直接在 buffer 里拿到数据 Step 8: 你处理业务逻辑,不需要再做 read()

代码解释:

// 你告诉 Proactor:帮我读数据,读完了回调我 AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); ByteBuffer buffer = ByteBuffer.allocate(1024); // 发起异步读操作,传入 buffer 和回调 channel.read(buffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer bytesRead, Void attachment) { // 内核已经读完了,数据在 buffer 里,你直接处理 buffer.flip(); byte[] data = new byte[buffer.limit()]; buffer.get(data); System.out.println("读到了: " + new String(data)); } @Override public void failed(Throwable exc, Void attachment) { System.out.println("读取失败"); } }); // 这里你可以去做别的事情,完全不阻塞 System.out.println("读操作已发起,我去干别的了");

为什么我们实际的业务场景中很少看到过他们呢?

实际上他们都被框架封装了:

  • BIO(阻塞IO):太原始,几乎没人直接用
  • NIO(非阻塞IO):虽然强大,但是API很难用,需要手动管理Channel、Buffer、Selector,还要处理各种异常状态。
  • AIO(异步IO):回调地狱,代码逻辑碎片化,也是很难调试

比如主流的Netty,底层使用的是NIO,Reactor模式(反应器模式),它把复杂的NIO细节全都封装起来了,只需要简单的几行Handler就可以处理高并发。

Tomcat,底层也是使用的NIO。

在高并发场景下,我们的目标是“处理业务逻辑”,而不是“手写网络协议栈”。

学习 NIO/AIO 的意义在于理解底层原理(比如为什么 NIO 能支撑百万连接,它是怎么通过 Selector 轮询的),而不是为了每天在业务代码里写selector.select()

为什么 Reactor 更常见?

因为 Proactor在 Linux 上不好使

  • Reactor:Linux 上有epoll,非常高效,Java NIO 就是用这个。
  • Proactor:Windows 上有IOCP,天生支持。但 Linux 上没有真正的 AIO,Java 的 AIO 在 Linux 上是用epoll模拟的,性能还不如 NIO。

所以现实中:

  • Netty(最流行的网络框架)用的是Reactor 模式
  • Redis用的是Reactor 模式
  • Nginx用的是Reactor 模式
  • Proactor只在 Windows 上比较常见

本章我们从 BIO 的阻塞困境出发,一路走到 NIO 的非阻塞革命,再到 AIO 的异步未来,并深入剖析了 Reactor 与 Proactor 这两种高性能服务器的核心骨架。至此,你已经掌握了 I/O 模型的理论根基。

下一章,我们将把这些理论落地到工业级实战——探究 Netty 为什么能支撑百万级并发,它又是如何将 Reactor 模式发挥到极致的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/12 10:53:49

如果你准备用 Codex API 做长期项目,可以先看看这个

最近整理一个自动化任务时&#xff0c;我又碰到 API 管理的问题。调用本身不复杂&#xff0c;麻烦的是后面要看余额、查日志、区分不同项目的 Key。 短期测试时&#xff0c;这些都可以靠手动记录。但项目一旦跑起来&#xff0c;手动维护就不太现实。 GetToken 这个站点主要提供…

作者头像 李华
网站建设 2026/7/12 10:52:03

高精度运动控制:A3908与PIC18LF4553的闭环伺服方案

1. 项目背景与核心需求在工业自动化、医疗设备和精密仪器领域&#xff0c;运动控制的精度往往直接决定最终产品的性能上限。我最近参与了一个高精度3D打印机的控制系统开发项目&#xff0c;客户要求Z轴定位精度达到1微米&#xff0c;这相当于人类头发直径的1/80。要实现这种级别…

作者头像 李华
网站建设 2026/7/12 10:50:18

Unity多人游戏开发:Mirror核心组件详解与实战指南

1. 项目概述&#xff1a;Mirror如何让Unity网络化变得简单如果你正在用Unity做游戏&#xff0c;并且想让你的游戏支持多人在线&#xff0c;那你大概率听说过或者正在被网络同步、客户端预测、服务器权威这些概念折磨。几年前&#xff0c;Unity自带的UNet&#xff08;已弃用&…

作者头像 李华
网站建设 2026/7/12 10:48:28

2026视频背景音乐保存为MP3方法,手机电脑免费工具教程

日常刷短视频、影视片段时&#xff0c;常会遇到适配度极高的背景音乐&#xff0c;很多人想将其单独保存为MP3格式&#xff0c;用于日常聆听、素材备用等。2026年主流的视频转音频、背景音乐提取工具分为手机端工具、微信小程序、电脑免费软件、在线网页网站四大类&#xff0c;不…

作者头像 李华
网站建设 2026/7/12 10:47:12

Mythos漏洞挖掘模型:可规模化自主发现的AI安全范式

1. 这不是一次普通模型发布&#xff1a;Mythos 的真实分量与行业震感你可能已经刷到过“Anthropic 发布 Claude Mythos”这条新闻&#xff0c;标题里带着“Preview”“Gated Release”这类字眼&#xff0c;很容易被当成又一场科技公司的例行发布会。但如果你真这么想&#xff0…

作者头像 李华