Java面试|第三讲:Java提供了哪些IO方式?什么是 NIO?

聊天开始.jpg

知识点储备

java提供了哪些io方式?

1.同步阻塞io: 传统的Java IO 方式,如InputStream,OutputStrea等。

2.多路复用的、同步非阻塞 IO(NIO): 在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序。

3.异步非阻塞 IO(AIO): 在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。

四种主要的IO模型

同步阻塞IO(Blocking IO)

在阻塞式 I/O 模型中,应用程序在从IO系统调用开始,一直到到系统调用返回,这段时间是阻塞的。返回成功后,应用进程开始处理用户空间的缓存数据
发起一个blocking socket的read读操作系统调用,大概流程是这样:

BIO.jpg

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

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

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

所以,blocking IO的特点就是在内核进行IO执行的两个阶段,用户线程都被block了

BIO优点: 程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。

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

1625276819(1).jpg

同步非阻塞NIO(None Blocking IO)


区分阻塞与非阻塞(blocking/non-blocking):

在进行阻塞IO时,当前线程会处于阻塞状态,无法从事其他任务;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。

流程如下:

**第一步: ** 在内核数据没有准备好的阶段,用户线程发起IO请求时,立即返回。用户线程需要不断地发起IO系统调用(可能会调用无数次)。

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

第三步: 用户线程解除block的状态,重新运行起来。用户线程终于真正读取到数据,继续执行。

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

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

注意:Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。

1625276938(1).jpg

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

IO多路复用模型可以避免上面同步非阻塞NIO模型中轮询等待的问题。

IO多路复用模型,建立在操作系统kernel内核能够提供的多路分离系统调用select/epoll基础之上的。多路复用IO需要用到两个系统调用(system call), 一个select/epoll查询调用,一个是IO的读取调用。

IO多路复用模型的基本原理就是select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接(这里有一个前提,需要将目标网络连接,提前注册到select/epoll的可查询socket列表中),当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。因此,通过一次select/epoll系统调用,就查询到到可以读写的一个甚至是成百上千的网络连接,大大提高了性能。

IO多路复用首先进行的是select/epoll调用,通过select/epoll调用后就知道了哪些socket有数据到达了,然后就可以进行read系统调动读取IO数据。

IO多路复用.jpg

调用流程:

第一步: 进行select/epoll系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。

当用户进程调用了select,那么整个线程会被block(阻塞掉)。

第二步: 用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。

第三步: 用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。

多路复用IO的优点: 用select/epoll的优势在于,它可以同时处理成千上万个连接(connection)。与一条线程维护一个连接相比,I/O多路复用技术的最大优势是:系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。

Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。

多路复用IO的缺点: 本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。

1625276992(1).jpg

异步IO模型(asynchronous IO)

同步或异步(synchronous/asynchronous):

同步调用: 需要用户线程不停的去问kernel内核,IO操作是否准备好?(包括数据准备、数据复制)等,kernel内核是不会主动告诉用户线程是否已经准备好IO操作的。

异步调用: 而异步调用,kernel内核准备好了IO操作(包括数据准备、数据复制),会主动通知用户线程,然后用户执行后续的业务操作。

AIO的基本流程是: 用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作

kernel的数据准备: 将数据从网络物理设备(网卡)读取到内核缓冲区。

kernel的数据复制: 将数据从内核缓冲区拷贝到用户程序空间的缓冲区。

AIO.jpg

调用流程:

第一步: 当用户线程调用了read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞。

第二步: 内核(kernel)就开始了IO的第一个阶段:准备数据。当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存)。

第三步: kernel会给用户线程发送一个信号(signal),或者回调用户线程注册的回调接口,告诉用户线程read操作完成了。

第四步: 用户线程读取用户缓冲区的数据,完成后续的业务操作。

异步IO模型的优点:

在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的。用户线程需要接受kernel的IO操作完成的事件,或者说注册IO操作完成的回调函数,到操作系统的内核。所以说,异步IO有的时候,也叫做信号驱动 IO 。

异步IO模型缺点:

需要完成事件的注册与传递,这里边需要底层操作系统提供大量的支持,去做大量的工作。

目前来说, Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是,就目前的业界形式来说,Windows 系统,很少作为百万级以上或者说高并发应用的服务器操作系统来使用。

而在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善。所以,这也是在 Linux 下,实现高并发网络编程时都是以 IO 复用模型模式为主。

考点分析

这个问题主要考察

(1)Java几种IO模型,

(2)对NIO的理解,什么是多路复用

(3)如果问的深入的一般还会问Netty的原理及源码(Netty知识点较多,不在本章介绍)。因为Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,在项目中有较多的应用,所以考察NIO还可能会考察Netty相关的知识。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享