JAVA补充面试题

五种IO模型与理解

IO最常接触的场景是读取文件和等待网络信息,一般而言,用户态是不能够直接操纵IO的,这就说明一定要向操作系统调用IO,主要分为两步:(1)操作系统的内核态会先等待数据准备好,(2)再从内核空间将数据拷贝到用户空间。

同步阻塞IO

以计算机网络编程为例,会调用read方法读取数据,而本质上是调用了内核的recvfrom方法。

  • 此方法会被阻塞住,直到数据报准备好,也就是1阶段。

  • 然后在数据报被从内核复制到用户空间这一过程中,该线程会再次阻塞,直到复制完成,这一过程对应上图的序号2的过程;

无论数据有没有被准备好,此IO过程都会被阻塞,数据准备好时只会阻塞2阶段,数据为准备好会阻塞1,2两个阶段。

同步非阻塞IO

这个过程分为了两种情况:

  • 数据未准备好时,会一直调用recvfrom函数,如果数据没准备好会返回EWOULDBLOCK错误。即不会将用户进程(线程)至于阻塞状态。

  • 数据准备好后,此时recvfrom系统调用,用户进程(线程)还是会阻塞,直到内核中的数据报已经拷贝到了用户空间,此时用户进程(线程)才会被唤醒来处理接收的数据报。

但是,进程(线程)不断轮训,因此这是非常耗费CPU的。

IO多路复用模型

阻塞和非阻塞模型的缺点是一个线程只能监控一个IO(也可以称为文件描述符fd)。如果系统并发度很高,线程就会大量创建,并不断轮训。系统资源会被大量浪费。

IO多路复用的思想是用一个线程监听多个文件描述符。当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。

IO复用模型的思路就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,select函数监控的fd中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。

信号驱动IO

IO多路复用解决了一个线程监控多个fd的问题。但是还有一个大问题,等待数据准备中一直要轮询,这不是很傻逼吗?于是就有了信号驱动IO,当数据准备好后通知线程,而不需要轮询。

首先需要在线程和fd之间建立一个SIGIO信号联系,这是通过sigaction建立的,当内核数据准备好后通过SIGIO通知线程。线程再调用recvfrom从内核拷贝数据到用户空间。

信号驱动IO模型在等待数据报期间是不会阻塞的,即用户进程(线程)发送一个sigaction系统调用后,此时立刻返回,并不会阻塞,然后用户进程(线程)继续执行;当数据报准备好时,此时内核就为该进程(线程)产生一个SIGIO信号,此时该进程(线程)就发生一次recvfrom系统调用将数据报从内核复制到用户空间,注意,这个阶段是阻塞的。

异步IO模型

异步IO最大的优点是在准备数据和拷贝数据两个过程都不会被阻塞。

  • 用户进程发生系统调用时,会立刻返回,不被阻塞。执行其他任务。
  • 当内核把数据拷贝完毕后,通知线程数据已经准备好,这个过程也不会阻塞。

异步IO模型跟信号驱动IO模型的区别在于当内核准备好数据报后,对于信号驱动IO模型,此时内核会通知用户进程说数据报准备好啦,你需要发起系统调用来将数据报从内核拷贝到用户空间,此过程是同步阻塞的;而对于异步IO模型,当数据报准备好时,内核不会再通知用户进程,而是自己默默将数据报从内核拷贝到用户空间后然后再通知用户进程说,数据已经拷贝到用户空间啦,你直接进行业务逻辑处理就行。

Java中的IO模型,BIO,NIO,AIO

BIO(Blocking I/O)同步阻塞IO

在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量.

NIO(Non-blocking/New I/O)多路复用IO

Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

AIO(Asynchronous I/O)异步IO

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。