背景
同步(Synchronous):意味着“内核拷贝数据到用户空间”这个动作,必须由用户线程自己来完成。
非阻塞(Non-blocking):意味着如果没有数据,read() 立即返回,不卡在那里。 同步和非阻塞关系,好像啊
你的直觉非常敏锐!在很多人的初次理解中,确实会把这两个概念混为一谈。
简单来说:“非阻塞”决定了你“等不等”数据;而“同步”决定了数据准备好后“谁动手”去搬。
为了让你彻底分清,我们可以用**“取外卖”**这个生活场景来做类比:
1. 阻塞 vs 非阻塞 (待餐阶段)
这取决于你在餐点没好时,你的状态是什么。
-
阻塞 (Blocking):你坐在店里死等,盯着出餐口,饭没好你哪也不去,啥也不干。
-
非阻塞 (Non-blocking):你进店问一句:“饭好了吗?”。老板说:“没呢”。你扭头就走,去路边玩会手机,过两分钟再回来问一句。这就是“非阻塞”,你没有被“卡住”。
2. 同步 vs 异步 (搬餐阶段)
这取决于饭好了以后,谁把饭送到你手上。
-
同步 (Synchronous):老板喊:“饭好了,自己来拿!”。于是你亲自走过去,把外卖从柜台提到你自己手里。“亲自去提”这个动作就是同步。 即使是非阻塞 IO,在
read()调用发现有数据时,线程也必须停下手中的活,亲自把数据从内核搬到内存。 -
异步 (Asynchronous):老板说:“饭好了,外卖员会送到你家门口”。你只需要在家等电话,饭已经已经在你桌子上了。“数据被自动搬好”就是异步。
3. 组合起来看:同步非阻塞 (NIO)
当我们说 Java NIO 是 “同步非阻塞” 时,它的完整行为是:
-
非阻塞部分:线程调
read(),如果内核缓冲区没数据,立马返回 0,线程去干别的(比如去巡逻别的 Channel)。 -
同步部分:当 Selector 告诉线程“有数据了”,线程必须回来执行一次
read()。在这个read()执行期间,CPU 会从用户态切换到内核态,由线程负责把数据从内核拷贝到 JVM 内存。在这个搬运的过程中,线程是不能干别的事情的。
4. 为什么会有这种“好像”的感觉?
因为在现代高并发编程中,我们几乎不会单独讨论“同步阻塞”之外的组合。
-
BIO (同步阻塞):最老土,一人盯一个,效率低。
-
NIO (同步非阻塞):目前的主流。它通过
Selector把“等数据”的过程变成了“批量等”,虽然“搬数据”还是得亲自来,但比起 BIO 已经节省了海量的线程资源。 -
AIO (异步非阻塞):最理想。内核不仅帮你等,还帮你搬。
底层真相:在 Linux 系统中,其实并没有真正完美的“异步读”系统调用(虽然有 AIO 原生接口但限制很多)。所以 Java 的 AIO 在 Linux 上往往也是用多路复用(NIO)模拟出来的。这就是为什么大家更倾向于研究 NIO。
总结对比
| 术语 | 关注点 | 核心表现 |
| 非阻塞 | 调用返回时刻 | 没数据也别让我挂起,直接返回结果(哪怕是空结果)。 |
| 同步 | 数据拷贝参与者 | 只要我没看到数据进我口袋,我就得亲自去拿。 |
既然你已经搞清楚了同步和非阻塞的区别,想不想看看在真实的字节面试中,面试官会怎么用“零拷贝(Zero-copy)”这个概念来进一步追问你关于数据搬运的性能优化?