JAVA BIO NIO AIO 详解!!!

Linux 同时被 3 个专栏收录
2 篇文章 0 订阅
3 篇文章 0 订阅
1 篇文章 0 订阅

IO 之 BIO NIO AIO



前言

在学习之前,先把基础知识拿来铺垫 !

1.1 什么是IO

在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等。

1.2 内核缓冲区&进程缓冲区

磁盘是数据块的集合,内核会对磁盘上的数据块做缓冲。缓冲过程如下
  1. 内核将磁盘上的数据块复制到内核缓冲区中,当一个用户空间中的进程要从磁盘上读数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程的缓冲区中。
  2. 当进程所要求的数据块不在内核缓冲区时,内核会把相应的数据块加入到请求队列,然后把该进程挂起,接着为其 他进程服务。
  3. 一段时间之后(其实很短的时间),内核把相应的数据块从磁盘读到内核缓冲区,然后再把数据复制到进程的缓冲区中,最后唤醒被挂起的进程。

用户缓冲区的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。

1.3 系统调用

用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。
read系统调用,并不是把数据直接从物理设备,读数据到内存,是把数据从内核缓冲区复制到进程缓冲区 。
write系统调用,也不是直接把数据,写入到物理设备,是把数据从进程缓冲区复制到内核缓冲区。

这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。

1.4 同步与异步

同步

对于同步型的调用,应用层需要自己去向系统内核问询,如果数据还未读取完毕,那此时读取文件的任务还未完成,应用层根据其阻塞和非阻塞的划分,或挂起或去做其他事情;如果数据已经读取完毕,那此时系统内核将数据返回给应用层,应用层即可以用取得的数据做其他相关的事情。

异步
而对于异步型的调用,应用层无需主动向系统内核问询,在系统内核读取完文件数据之后,会主动通知应用层数据已经读取完毕,此时应用层即可以接收系统内核返回过来的数据,再做其他事情。

比如我去银行办理业务,可能选择排队等候,也可能取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了.
排队等候就是同步等待消息,而取小纸条等待别人通知就是异步等待消息.在异步消息处理中,等待办理业务的人往往注册一个回调机制,在所等待的事件被触发时由柜台的人通过小纸条上的号码找到等待该事件的人.

1.5 阻塞与非阻塞

阻塞

无数据准备好,系统调用比如read,recvfrom就会挂起,等到有数据准备好或者有数据了才继续执行系统调用,最后才从系统调用的函数中返回。

非阻塞

这里的非阻塞是通过忙轮询去检测是否有数据准备好,没有数据准备好就一直轮询,直到有数据准备好了可以开始进行数据的复制了为止。

继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息之外不能做其它的事情,那么该机制就是阻塞的

相反,有的人喜欢在银行办理这些业务的时候一边打玩手机一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待


2. 五种主要的IO模型

(1)同步阻塞IO(Blocking IO)

(2)同步非阻塞IO(Non-blocking IO)

(3)IO多路复用(IO Multiplexing)

(4)信号驱动IO(SIGIO)

(5)异步IO(Asynchronous IO)

3、使用步骤

3.1 同步阻塞BIO(Blocking IO)


同步阻塞BIO

示例:
(1)当用户线程调用了read系统调用,内核(kernel)就开始了IO的第一个阶段:准备数据。很多时候,数据在一开始还没有到达(比如,还没有收到一个完整的Socket数据包),这个时候kernel就要等待足够的数据到来。

(2)当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。

(3)从开始IO读的read系统调用开始,用户线程就进入阻塞状态。一直到kernel返回结果后,用户线程才解除block的状态,重新运行起来。

Blocking IO的特点是:在内核进行IO执行的两个阶段,用户线程都被block了。

BIO的优点:

  1. 程序简单。
  2. 在阻塞等待数据期间,用户线程挂起。
  3. 用户线程基本不会占用 CPU 资源。

BIO的缺点:

一般情况下,会为每个连接配套一条独立的线程,或者说一条线程维护一个连接成功的IO流的读写。在并发量小的情况下,这个没有什么问题。但是,当在高并发的场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上,BIO模型在高并发场景下是不可用的。

3.2 同步非阻塞NIO(None Blocking IO)


 同步非阻塞NIO

(1)在内核数据没有准备好的阶段,用户线程发起IO请求时,立即返回。用户线程需要不断地发起IO系统调用。

(2)内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。

(3)用户线程才解除block的状态,重新运行起来。经过多次的尝试,用户线程终于真正读取到数据,继续执行。

NIO的优点:

每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。

NIO的缺点:

需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。

总之,NIO模型在高并发场景下,也是不可用的。一般 Web 服务器不使用这种 IO 模型。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。java的实际开发中,也不会涉及这种IO模型。

再次说明,Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。

3.3 IO多路复用模型(I/O multiplexing)


IO多路复用模型

select,poll,epoll都是IO多路复用的机制。所谓I/O多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
select模式(时间复杂度O(n)):
在Linux中有一段这样的描述:

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.

大致意思如下:
select()和 pselect()允许程序监视多个文件描述符,直到一个或多个文件描述符“准备好”进行某类I/O操作(例如,可能的输入)。(例如,如果描述符没有被阻塞,它就可以执行读操作)。
大致过程如下:
(1)进行select系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表(fds),一旦某个描述符就绪,select就会返回。
(2)拿到文件描述符集合后,进行遍历(在用户空间处理),获取到就绪的文件描述符,发起read()系统调用,此时的调用都是有效的,即所有调用的文件描述符处于就绪状态。

poll模式(时间复杂度O(n)):
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。

优点:
跟NIO相比,就减少了不断轮询的成本,减少了CPU的压力。

缺点:
select系统调用需要将文件描述符在用户空间和内核空间进行传输,用户线程需自行判断那些文件描述符处于就绪状态。属于同步IO

epoll模式(时间复杂度O(1)):
Linux 系统中对epoll有这样一段描述:

The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.

大致意思是:

epoll API 执行与poll() 类似的任务:监视多个文件描述符,以确定它们中的任何一个是否可以进行I/O。

同时,此模式涉及到内存地址映射(mmap),简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。

大致过程如下:
1.用户空间和内核空间会创建一个内存地址映射,将所有需要触发io的文件描述符维护在内核空间可以直接访问的内存中。
2.当触发epoll函数后,内核空间会返回已经就绪的文件描述符所在的内存地址映射位置,用户空间通过映射关系获取到就绪的文件描述符。
3.用户空间线程发起read()/write()调用,将就绪的文件描述符作为参数传递。

优点:
跟select模式相比,通过共享空间,减少了文件描述符的拷贝和遍历的过程。
缺点:
属于同步IO,不论select 或者 epoll 都需要进行read()/write()系统调用,此时用户线程处于阻塞状态

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现,Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。

3.4 信号驱动IO(SIGIO)


在这里插入图片描述

应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。信号驱动 I/O 的 CPU 利用率很高。

3.5 异步IO模型(asynchronous IO)


异步IO模型

应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。


总结


IO模型对比

本文以向面试官吹牛逼为己任,这些内容面试前还是要看看的!!
如有问题,欢迎留言讨论!!!!

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值