最近越来越认为,在讲解技术相关问题时,大白话固然很重要,通俗易懂,让人有想读下去的欲望。但几乎所有的事,都有两面性,在看到其带来好处时,不妨想想是否也引入了不好的地方。
例如在博客中,过于大白话的语言的确会让你阅读起来更加顺畅,也更容易理解。但这都是其他人理解,已经咀嚼过了的,人家是已经完全理解了,你从这些信息中大概可能会观察不到全貌。所以,适当的白话是很好的,但这个度得控制一下。
接下来切入正文。
相信大家经常看到这个问题:
BIO、NIO 和 AIO 有什么区别?看到这个问题,可能你脑海中就会浮现以下这些字眼。比如 BIO 就是如果从内核获取数据会一直阻塞,直到数据准备完毕返回。再比如 NIO,内核在数据没有准备好时不会阻塞住,调用程序会一直询问内核数据是否 Ready。
虽然是正确的,字数也很少。但是这样一来,你看这些概念就不是理解,而是背诵了。其实 BIO 和 NIO 这类的名词还有一个共同的名字叫——IO模型,总共有:
文章图片
IO 模型
由于信号驱动 IO 在实际中不常用,我们主要讲以下四种模型:
- 同步阻塞
- 同步非阻塞
- IO 多路复用
- 异步 IO
假设此时客户端正在发送一些数据到服务器,并且数据已经通过客户端的协议栈、网卡,陆陆续续的到达了服务器这边的内核态 Buffer 中了。不清楚用户态和内核态区别的可以看看《简单聊聊用户态和内核态的区别》
对数据在网络中是如何传输的细节感兴趣的,可以去看看我之前写的文章 《请求数据包从发送到接收,都经历了什么?》。
同步阻塞 BIO
我们需要知道,内核在处理数据的时候其实是分成了两个阶段:
- 数据准备
- 数据复制
【java|图解四种 IO 模型】当调用线程发起
read
系统调用时,如果此时内核数据还没有 Ready,调用线程会阻塞住,等待内核 Buffer 的数据。内核数据准备就绪之后,会将内核态 Buffer 的数据复制到用户态 Buffer 中,这个过程中调用线程仍然是阻塞的,直到数据复制完成,整个流程用图来表示就张这样:文章图片
同步非阻塞 NIO
相信大家知道 Java 中有个包叫
nio
,但那跟我们现在正在讨论的 NIO 不是同一个概念。现在正在讨论的是 Non-Blocking IO,代表同步非阻塞,是一种基础的 IO 模型。而
nio
包则是 New IO,里面的 IO 模型实际上是 IO多路复用,大家不要搞混淆了。有了 BIO 的基础,这次我们直接来看图:
文章图片
NIO
还是分为两个阶段来讨论。
数据准备阶段。此时用户线程发起 read 系统调用,此时内核会立即返回一个错误,告诉用户态数据还没有 Read,然后用户线程不停地发起请求,询问内核当前数据的状态。
数据复制阶段。此时用户线程还在不断的发起请求,但是当数据 Ready 之后,用户线程就会陷入阻塞,直到数据从内核态复制到用户态。
稍微总结一下,如果内核态的数据没有 Ready,用户线程不会阻塞;但是如果内核态数据 Ready 了,即使当前的 IO 模型是同步非阻塞,用户线程仍然会进入阻塞状态,直到数据复制完成,并不是绝对的非阻塞。
那 NIO 的好处是啥呢?显而易见,实时性好,内核态数据没有 Ready 会立即返回。但是事情的两面性就来了,频繁的轮询内核,会占用大量的 CPU 资源,降低效率。
IO 多路复用
IO 多路复用实际上就解决了 NIO 中的频繁轮询 CPU 的问题。在之前的 BIO 和 NIO 中只涉及到一种系统调用——
read
,在 IO 多路复用中要引入新的系统调用——select
。read
用于读取内核态 Buffer 中的数据,而 select
你可以理解成 MySQL 中的同名关键字,用于查询 IO 的就绪状态。在 NIO 中,内核态数据没有 Ready 会导致用户线程不停的轮询,从而拉满 CPU。而在 IO 多路复用中调用了
select
之后,只要数据没有准备好,用户线程就会阻塞住,避免了频繁的轮询当前的 IO 状态,用图来表示的话是这样:文章图片
IO 多路复用
异步 AIO
该模型的实现就如其名,是异步的。用户线程发起
read
系统调用之后,无论内核 Buffer 数据是否 Ready,都不会阻塞,而是立即返回。内核在收到请求之后,会开始准备数据,准备好了&复制完成之后会由内核发送一个 Signal 给用户线程,或者回调用户线程注册的接口进行通知。用户线程收到通知之后就可以去读取用户态 Buffer 的数据了。
文章图片
AIO
由于这种实现方式,异步 IO 有时也被叫做信号驱动 IO。相信你也发现了,这种方式最重要的是需要 OS 的支持,如果 OS 不支持就直接完蛋。
Linux 系统在 2.6 版本的时候才引入了异步IO,不过那个时候并不算真正的异步 IO,因为内核并不支持,底层其实是通过 IO 多路复用实现的。而到了 Linux 5.1 时,才通过
io_uring
实现了真 AIO。推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- 【C】题目|【C语言】题集 of ⑥
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- Spring注解驱动第十讲--@Autowired使用