曾经有人面试问了我一个Java 基础问题:你可以说一说Java IO模型有哪些吗?我听了完全蒙圈,啥是IO模型啊,只用过IO操作,没关注过什么是IO有什么类型啊,于是直接回复说:我没有了解过,不是很明白你想问的点是什么。于是就。。。。。。
回来后,百度了一下,记录吧。
-----------------------------------------------------------------
Linux IO模型
I/O 在计算机中指Input/Output,即输入输出,比如
以一次文件读取为例,我们需要将磁盘上的数据读取到用户空间,那么这次数据转移操作其实就是一次I/O操作,也就是一次文件I/O
我们每天都浏览着各种各样的网页,在我们每请求一个网页,服务器通过网络将一个个的分组数据发送给我们,应用程序从TCP缓冲区将数据复制到用户空间的过程也是一次I/O,即一次网络I/O
由于Java这样的高级语言,它对底层操作系统的各种I/O模型进行了封装,使得我们可以很轻松的进行开发,因此很少关注Java中各种I/O模型,以及它们和操作系统之间的关联
1.阻塞I/O模型 Blocking I/O
最传统的I/O模型,即在读写数据过程中会阻塞。例如在应用进程调用recvfrom,系统调用直到数据从内核从复制到用户空间,应用进程在这一段时间内一直是被阻塞的。
这种模型适合并发量较小的对时延不敏感的系统
2.非阻塞I/O模型 Non-Blocking I/O
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时.流程是这个样子:
1. 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。
2. 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。
3.一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。
3.I/O复用模型 IO multiplexing
在Liunx中为我们提供了select/poll,也就是管道。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
1.当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。
2.这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。
select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接
4.信号驱动式I/O模型
1.首先开启套接字的信号驱动I/O功能,通过sigaction系统调用安装一个信号处理函数,系统调用立即返回,进程继续工作
2.当数据包准备好时内核产生一个SIGIO信号通知,
3.通过recvfrom调用读取数据报。
信号驱动式I/O模型的优点是在数据报到达期间进程不会被阻塞,只要等待信号处理函数的通知即可
5.异步 I/O(asynchronous IO)
1. 用户进程发起read操作之后,立刻就可以开始去做其它的事。
2.从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。
3.然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
同步I/O 与 异步 I/O Synchronous VS Asynchronous
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。
按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。
其中non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。
而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
--------------------------------------------------------------------------
Java I/O
BIO 阻塞I/O 编程
使用BIO通信模型的服务端,通常通过一个独立的Acceptor线程负责监听客户端的连接,监听到客户端连接请求后为每一个客户端创建一个新的线程链路进行处理,处理完成通过输出流回应客户端,线程消耗,这就是典型一对一答模型。
每当一个连接接入时我们都需要new一个线程进行处理,这显然是不合适的,因为一个线程只能处理一个连接,如果在高并发的情况下,我们的程序肯定无法满足性能需求,同时我们对线程创建也缺乏管理
伪异步I/O
为了改进BIO 这种模型我们可以通过消息队列和线程池技术对他加以优化,我们称它为伪异步I/O
每当有新连接接入,都将投递给线程池进行处理,由于设置了线程池大小和阻塞队列大小,因此在并发情况下都不会导致服务崩溃,但是如果并发数大于阻塞队列大小,或服务端处理连接缓慢时,阻塞队列无法继续处理,会导致客户端连接超时,影响用户体验
NIO 非阻塞I/O编程
NIO 弥补了同步阻塞I/O的不足,它提供了高速、面向块的I/O。
Buffer: Buffer用于和NIO通道进行交互。数据从通道读入缓冲区,从缓冲区写入到通道中,它的主要作用就是和Channel进行交互。
Channel: Channel是一个通道,可以通过它读取和写入数据,通道是双向的,通道可以用于读、写或者同时读写。
Selector: Selector会不断的轮询注册在它上面的Channe,如果Channel上面有新的连接读写事件的时候就会被轮询出来,一个Selector可以注册多个Channel,只需要一个线程负责Selector轮询,就可以支持成千上万的连接,可以说为高并发服务器的开发提供了很好的支撑。
AIO 异步I/O 编程
通过学习Lunix底层I/O模型和JavaI/O模型,发现上层只是对底层的抽象和封装,
BIO其实是对阻塞I/O模型的实现
NIO是对I/O复用模型的实现
AIO是对信号驱动I/O的实现
在JDK 1.4之前,基于Java的所有Socket通信都使用了同步阻塞模式(Blocking I/O),这种一请求一应答的通信模型简化了上层开发,但性能可靠性存在巨大瓶颈,对高并发和低时延支持不好
在JDK 1.4之后,提供了新的NIO(New I/O)类库,Java也可以支持非阻塞I/O了,新增了java.nio包,提供了很多异步I/O开发的API和类库。
JDK 1.7发布后,将原来的NIO类库进行了升级,提供了AIO功能,支持基于文件的异步I/O操作和针对套接字的异步I/O操作等功能
以上信息,参考了以下博文,仅用于个人学习和知识积累。
https://blog.csdn.net/yjp198713/article/details/79363822
https://juejin.cn/post/6844903839439519758#heading-11