一、基础概念: 阻塞/非阻塞、同步/异步
1.1 阻塞/非阻塞和同步/异步这两个概念的区别
1.1.1 他们不是一回事,是两个不同维度
概念 | 讨论的层面 | 问题核心 |
---|---|---|
阻塞/非阻塞 | 线程等待状态 | 当线程发起I/O操作时,是"停下来等结果",还是"立刻返回自己干别的" |
同步/异步 | 结果通知方式 | 谁负责通知I/O操作完成?是自己主动去取,还是系统回调告诉你 |
可以这么理解:
- "阻塞/非阻塞"->线程等不等
- "同步/异步"->结果谁来告诉你
两者的本质区别总结一句话: "阻塞/非阻塞"关注的是线程是否空等,"同步/异步"关注的是I/O完成后通知的方式。
1.1.2 一个生活例子: 点外卖 🍔
我们用"点外卖"的例子讲这四种组合:
①同步阻塞(最传统的方式)
你打电话点外卖 → 然后就在家门口等着,直到外卖送到,什么都不干。
- 线程: 等待中(被阻塞)
- 结果通知: 靠你自己等
- 举例: Java的BIO模型
java
# 类比
read() // 没数据?我就一直等...
②同步非阻塞
你打电话点外卖 → 然后每隔一会儿跑去门口看看外卖到了没。
- 线程: 没有被阻塞(可以做别的事)
- 结果通知: 仍然是你自己反复检查
- 举例: Java 的 NIO(Selector 轮询)
java
# 类比
while(true){
if(channel.read(buffer) > 0){
// 终于有数据了!
} else {
// 没有数据,继续干别的
}
}
③异步阻塞(理论上存在但不常用)
你打电话点外卖 → 外卖系统说"到了我会打你电话",但你却一直拿着手机等电话(别的事也不做)。
- 线程: 阻塞(在等通知)
- 结果通知: 系统(外卖)回调告诉你
实际系统中不常用
④异步非阻塞(最现代、最高效)
你打电话点外卖 → 系统登记好 → 你继续干别的,外卖送到后系统自动发短信通知你"到了"。
- 线程: 不阻塞
- 结果通知: 系统主动回调
- 举例: Java AIO(异步通道回调 CompletionHandler)
java
# 类比
channel.read(buffer, attachment, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
// 有数据可读了!系统通知我来处理
}
});
二、三种I/O模型概述

2.1 BIO(同步阻塞IO)
BIO(Blocking I/O):指的是当线程执行 I/O 操作时,如果数据没准备好,就会一直等待,直到数据可用或发生异常才返回。
在 Java 网络编程中,BIO 典型表现就是:
一个连接对应一个线程,读写操作会阻塞线程。

2.2 NIO(同步非阻塞IO/多路复用IO)
目的是:让一个线程能同时管理多个连接,实现高并发的网络通信。
与BIO的明显区别是,发起第一次请求后,线程并没有被阻塞,它反复检查数据是否准备好,把原来大块不能用的阻塞时间分成了许多"小阻塞"(检查),所以进程不断有机会被执行。这个检查有没有准备好数据的过程有点类似于"轮询"。
核心组件:
- Channel:类似传统 I/O 的流,但支持非阻塞操作
- Buffer:用于数据的读写中转
- Selector(选择器):核心机制,将多个 Channel 注册到一个 Selector 上,由它监控这些通道的 I/O 事件
IO多路复用
NIO 是上层 API 框架,是 Java 对这种能力的封装;NIO 的 Selector 就是"多路复用器",本质上就是在调用 select / epoll。
- select: 注册事件由数组管理, 数组是有长度的, 32位机上限1024, 64位机上限2048。轮询查找时需要遍历数组。
- poll:把select的数组采用链表实现,因此没了最大数量的限制
- epoll方式:基于事件回调机制,回调时直接通知进程,无须使用某种方式来查看状态。
2.3 AIO(异步非阻塞IO)
核心思想: 应用线程发起 I/O 操作后立即返回,由操作系统在操作完成时自动通知应用(回调)。
也就是说:
线程发起 I/O 请求后不用自己轮询、也不会阻塞,当数据准备好(或者写完)时,系统会调用回调函数 CompletionHandler 来处理结果。