BIO、NIO、AIO,还傻傻分不清?

Java技术栈

共 3035字,需浏览 7分钟

 ·

2021-06-23 22:52

点击关注公众号,Java干货及时送达

作者:rickiyang
出处:www.cnblogs.com/rickiyang/p/11074238.html

我们知道java的I/O模型一共有四种,分别是:传统的BIO,伪异步I/O,NIO和AIO。

为了澄清概念和分清区别,我们还是先简单的介绍一下他们的概念,然后再去比较优劣。

1.概念澄清

1.1 BIO

BIO,即Blocking I/O。

网络编程的基本模型是Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的Ip 地址和监听端口) ,客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建在成功,双方就可以通过网络套接字( Socket ) 进行通信。

在基于传统同步阻塞模型开发中, ServerSocket 负责绑定IP 地址,启动监听端口:Socket 负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信

BIO通信模型图:

解释一下上图:

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端, 统程销毁。这就是典型的一请求一回答通信模型。

对于这种IO模型我们知道:用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。即在读写数据过程中会发生阻塞现象。

1.2 伪异步IO

为了解决同步阻塞 I/O 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M: 线程池最大线程数N 的比例关系,其中M 可以远远大于N。通过线程地可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。

伪异步IO通信模型图:

采用线程池和任务队列可以实现伪异步I/O通信框架。当有新的客户端接入时,将客户端的Socket 封装成一个Task (该任务实现java.lang. Runnable 接口)投递到后端的线程池中进行处理, JDK 的线程将维护一个消息队列和N个活跃线程, 对消息队列中的任务进行处理。

由于统程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的, 无论多少个客户端并发访问, 都不会导致资源的耗尽和省机。更多 Java 教程和示例代码:https://github.com/javastacks/javastack

伪异步I/O 通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。

伪异步I/O 实际上仅仅是对之前I/O 线程模型的一个简单优化,它无法从根本上解决同步I/O 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。

  1. 服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms;
  2. 采用伪异步I/O 的线程在读取故障服务节点的响应,由于读/取输入流是阻塞的,它将会被同步阻塞60s;
  3. 假如所有的可用线程都被故障服务器阻塞,那后续的所有的I/O消息都将在队列中排队;
  4. 由于线程地采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞;
  5. 由于前端只有一个Accptor 线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时;
  6. 由于几于所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。

如何破解这个难题?下面我们再看一下NIO。微信搜索Java技术栈,在后台回复:面试,可以获取我整理的 Java/ IO 系列面试题和答案,非常齐全。

1.3 NIO

NIO,很多人叫他New I/O,由于之前老的I/O 类库是阻塞I/O ,New I/O 类库的目标就是要让Java 支持非阻塞I/O,所以,更多的人喜欢称之为非阻塞I/O(Non-block I/O)。

与Socket类和ServerSocket 类相对应, NIO也提供了SocketChannel 和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自己的需要来选择合适的模式。

一般来说,低负载、低并发的应用程序可以选择同步阻塞I/O以降低编程复杂度:对于高负载、高并发的网络应用,需要使用NIO 的非阻塞模式进行开发。Spring Boot 学习笔记,这个分享给你。

前面我们已经对NIO进行了介绍,我们知道NIO中引入了缓冲区Buffer,通道Channel和多路复用器Selector的概念。一个多路复用器Selector 可以同时轮询多个Channel,而Channel又是全双工的,同时支持读写操作,使用NIO 编程的优点总结如下:

  1. 客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECT 等待后续结果,不需要像之前的客户端那样被同步阻塞。
  2. SocketChannel 的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O 通信线程就可以处理其他的链路,不需要同步等待这个链路可用。
  3. 线程模型的优化:由于JDK 的Selector 在Linux 等主流操作系统上通过epoll 实现,它没有连接句柄数的限制(只受限于操作系统的最大句柄数或者对单个进程的句柄限制),这意味着一个Selector 线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降。因此,它非常适合做高性能、高负载的网络服务器。
1.4 AIO

NIO 2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。

异步通道提供以下两种方式获取获取操作结果:

▷通过java.util.concurrent.Future 类来表示异步操作的结果;

▷在执行异步操作的时候传入一个java.nio.channels;

NIO 2.0 的异步套接字通道是真正的异步非阻塞I/O ,对应于UNIX 网络编程中的事件 驱动I/O (AIO) 。它不需要通过多路复用器( Selector) 对注册的通道进行轮询操作即可实 现异步读写,从而简化了NIO 的编程模型。

前面对不同的I/O模型进行了简单介绍,不同的I/O 模型由于线程模型、API 等差别很大,所以用法的差异也非常大。

我们用一个表格来做一个统一说明:

最后,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java/ IO 系列面试题和答案,非常齐全。






关注Java技术栈看更多干货



获取 Spring Boot 实战笔记!
浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报