
面试官:BIO、NIO、AIO之间有什么区别?
四、NIO
NIO,英文全称:Non-blocking-IO,一种同步非阻塞的 I/O 模型。
在 Java 1.4 中引入,对应的代码在java.nio
包下。
与传统的 IO 不同,NIO 新增了 Channel、Selector、Buffer 等抽象概念,支持面向缓冲、基于通道的 I/O 数据传输方法。
NIO 模型图,如下图:
与此同时,NIO 还提供了与传统 BIO 模型中的 Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现。
NIO 这两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的 BIO 一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发效率和更好的维护性;对于高负载、高并发的(网络)应用,使用 NIO 的非阻塞模式来开发可以显著的提升数据传输效率。
在介绍样例之前,我们先看一下 NIO 涉及到的核心关联类图,如下:
上图中有三个关键类:Channel 、Selector 和 Buffer,它们是 NIO 中的核心概念。
- Channel:可以理解为通道;
- Selector:可以理解为选择器;
- Buffer:可以理解为数据缓冲区;
从名词上看感觉很抽象,我们还是用之前介绍的城市交通工具来继续形容 NIO 的工作方式,这里的 Channel 要比 Socket 更加具体,它可以比作为某种具体的交通工具,如汽车或是高铁、飞机等,而 Selector 可以比作为一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态,是已经出站还是在路上等等,也就是说它可以轮询每个 Channel 的状态。
还有一个 Buffer 类,你可以将它看作为 IO 中 Stream,但是它比 IO 中的 Stream 更加具体化,我们可以将它比作为车上的座位,Channel 如果是汽车的话,那么 Buffer 就是汽车上的座位,Channel 如果是高铁上,那么 Buffer 就是高铁上的座位,它始终是一个具体的概念,这一点与 Stream 不同。
Socket 中的 Stream 只能代表是一个座位,至于是什么座位由你自己去想象,也就是说你在上车之前并不知道这个车上是否还有座位,也不知道上的是什么车,因为你并不能选择,这些信息都已经被封装在了运输工具(Socket)里面了。
NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 传输过程中涉及到的信息具体化,让程序员有机会去控制它们。
当我们进行传统的网络 IO 操作时,比如调用write()
往 Socket 中的SendQ
队列写数据时,当一次写的数据超过SendQ
长度时,操作系统会按照SendQ
的长度进行分割的,这个过程中需要将用户空间数据和内核地址空间进行切换,而这个切换不是程序员可以控制的,由底层操作系统来帮我们处理。
而在Buffer
中,我们可以控制Buffer
的capacity
(容量),并且是否扩容以及如何扩容都可以控制。
理解了这些概念后我们看一下,实际上它们是如何工作的呢?
我们一起来看看代码实例!
服务端操作,样例程序如下:
客户端操作,样例程序如下:
最后,依次启动服务端、客户端,看看控制台输出情况如何。
服务端控制台结果如下:
客户端控制台结果如下:
从编程上可以看到,NIO 的操作比传统的 IO 操作要复杂的多!
Selector 被称为选择器 ,当然你也可以翻译为多路复用器 。它是Java NIO 核心组件中的一个,用于检查一个或多个 Channel(通道)的状态是否处于连接就绪、接受就绪、可读就绪、可写就绪。
如此可以实现单线程管理多个 channels 的目的,也就是可以管理多个网络连接。
使用 Selector 的好处在于 :相比传统方式使用多个线程来管理 IO,Selector 使用了更少的线程就可以处理通道了,并且实现网络高效传输!
虽然 Java 中的 nio 传输比较快,为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?
从上面的代码中大家都可以看出来,除了编程复杂之外,还有几个让人诟病的问题:
- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%!
- 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高!
但是,Netty 框架的出现,很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题,关于 Netty 框架应用,会在后期的文章里进行介绍。
五、AIO
最后就是 AIO 了,全称 Asynchronous I/O,可以理解为异步 IO,也被称为 NIO 2,在 Java 7 中引入,它是异步非阻塞的 IO 模型。
异步 IO 是基于事件回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
具体的实例如下!
服务端操作,样例程序如下:
客户端操作,样例程序如下:
同样的,依次启动服务端程序,再启动客户端程序,看看运行结果!
服务端控制台结果如下:
客户端控制台结果如下:
这种组合方式用起来十分复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式,可以实现非常高的网络传输性能。
Netty 之前也尝试使用过 AIO,不过又放弃了!
六、小结
本文主要围绕 BIO、NIO、AIO 等模型,结合一些样例代码,做了一次简单的内容知识总结,希望对大家有所帮助。
内容难免有所遗漏,欢迎留言指出!
七、参考
1、JDK1.7&JDK1.8 源码
2、IBM - 许令波 -深入分析 Java I/O 的工作机制
3、Github - JavaGuide - IO总结
4、博客园 - 五月的仓颉 - IO和File
文章转载自公众号:Java极客技术
