⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 小哈学Java 「犬小哈」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这题筛的是什么人

不是让你背名词。面试官想看你能不能把同步/异步、阻塞/非阻塞这两组概念说清楚,以及理解不同 I/O 模型对线程资源和并发能力的真实影响。加分项:说出为什么 Netty 选了 NIO 而不是 AIO。

三句话讲完核心区别

  • BIO(同步阻塞):线程 read/write 后被挂起,干等到操作完成。一连接一线程,连接多了就炸。
  • NIO(同步非阻塞):线程发起 I/O 后立即返回,Selector 事件驱动检查就绪状态。核心三件套:Channel、Buffer、Selector。
  • AIO(异步非阻塞):线程发起 I/O 后立即返回,操作系统完成整个 I/O 后主动通知你,不用轮询。

实际选型:NIO 是当前高性能网络应用的绝对主流。 Netty、gRPC、Kafka 全用它。AIO 在 Linux 上实现不成熟,BIO 只适合低并发简单场景。

两把钥匙:同步/异步 × 阻塞/非阻塞

区分三者的本质就两组概念:

  • 同步 vs 异步:谁通知结果?同步 = 你自己问,异步 = 操作系统告诉你。
  • 阻塞 vs 非阻塞:等结果时线程干嘛?阻塞 = 傻等,非阻塞 = 立即返回做别的事。

组合起来一张表:

模型 同步/异步 阻塞/非阻塞 架构模式 线程模型
BIO 同步 阻塞 — 一连接一线程
NIO 同步 非阻塞 Reactor 少量线程管理大量连接
AIO 异步 非阻塞 Proactor 操作系统”送货上门”

代码对比:BIO 和 NIO 的差距

BIO(伪代码)

Socket clientSocket = serverSocket.accept(); // 阻塞等连接
new Thread(() -> {
InputStream in = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer); // <-- 阻塞,干等数据
// 处理数据...
}).start();

逻辑简单直白。但连接数一多,线程数线性增长,资源直接炸。

NIO(核心结构)

Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) continue;

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
SocketChannel sc = ssChannel.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = sc.read(buffer); // <-- 非阻塞
if (bytesRead > 0) { /* 处理数据 */ }
}
keyIterator.remove();
}
}

单线程管理大量连接,资源利用率高。代价是编程模型复杂得多——但这也是为什么你应该用 Netty 而不是自己撸的原因。

生产环境选型:Netty 是唯一正解

  1. 别直接用原生 NIO API——复杂且坑多。生产环境用 Netty 或 Apache Mina,它们封装好了心跳、重连、编解码。
  2. Selector 空轮询 Bug:某些 Linux 内核版本下 selector.select() 没事件也立即返回,CPU 直接 100%。Netty 通过重建 Selector 绕过了这个坑——这是你不该自己撸 NIO 的理由之一。
  3. Buffer 操作要小心:flip()、clear()、compact() 调用顺序搞反,数据读写直接乱套。被这玩意坑过的人,默默举手。

两个误区,踩一个面试就凉

  • “NIO 一定比 BIO 快”——错。连接少且活跃度高时,BIO 反而可能更优。NIO 的真正优势是用少量线程管理海量连接——高并发、长连接、低活跃度场景(聊天服务器、RPC 框架)才是它的主场。
  • “AIO 是 NIO 升级版,所以更好”——理论很美好,现实很骨感。AIO 在 Linux 上底层依赖 epoll 且用线程池模拟异步,性能提升不明显。真正的 AIO 实现是 Windows 的 IOCP,但 Java 服务端有几个跑 Windows 的?
文章目录
  1. 1. 这题筛的是什么人
  2. 2. 三句话讲完核心区别
  3. 3. 两把钥匙:同步/异步 × 阻塞/非阻塞
  4. 4. 代码对比:BIO 和 NIO 的差距
  5. 5. 生产环境选型:Netty 是唯一正解
  6. 6. 两个误区,踩一个面试就凉