来源:儒猿的大厂面试突击第三季

    1、面试题

    nio、bio、aio都是什么以及有什么区别?说说nio的原理?

    2、面试官心里分析

    如果聊io这块,我就必问这个问题,因为io的那些过于基础的知识,各种流的使用,是用来考察应届生和培训班刚出来的同学的,正常问一个有经验的开发人员,io这块就是聊聊几种io模式,以及同步、异步、阻塞和非阻塞几种io的概念。

    3、面试题剖析

    3.1 BIO

    这个其实就是最传统的网络通信模型,就是BIO,同步阻塞式IO,简单来说大家如果参加过几个月的培训班儿应该都知道这种BIO网络通信方式。就是服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建一个Socket和一个线程去跟那个Socket进行通信。

    然后客户端和服务端的socket,就进行同步阻塞式的通信,客户端socket发送一个请求,服务端socket进行处理后返回响应,响应必须是等处理完后才会返回,在这之前啥事儿也干不了,这可不就是同步么。

    这种方式最大的坑在于,每次一个客户端接入,都是要在服务端创建一个线程来服务这个客户端的,这会导致大量的客户端的时候,服务端的线程数量可能达到几千甚至几万,几十万,这会导致服务器端程序的负载过高,最后崩溃死掉。

    要么你就是搞一个线程池,固定线程数量来处理请求,但是高并发请求的时候,还是可能会导致各种排队和延时,因为没那么多线程来处理。
    image.png

    3.2 NIO

    JDK 1.4中引入了NIO,这是一种同步非阻塞的IO,基于Reactor模型。

    NIO中有一些概念:

    比如Buffer,缓冲区的概念,一般都是将数据写入Buffer中,然后从Buffer中读取数据,有IntBuffer、LongBuffer、CharBuffer等很多种针对基础数据类型的Buffer。

    还有Channel,NIO中都是通过Channel来进行数据读写的。

    包括Selector,这是多路复用器,selector会不断轮询注册的channel,如果某个channel上发生了读写事件,selector就会将这些channel获取出来,我们通过SelectionKey获取有读写事件的channel,就可以进行IO操作。一个Selector就通过一个线程,就可以轮询成千上万的channel,这就意味着你的服务端可以接入成千上万的客户端。

    这块其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。

    这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。

    这里的核心就是因为,一个客户端不是时时刻刻都要发送请求的,没必要死耗着一个线程不放吧,所以NIO的优化思想就是一个请求一个线程。只有某个客户端发送了一个请求的时候,才会启动一个线程来处理。

    所以为啥是非阻塞呢?因为无论多少客户端都可以接入服务端,客户端接入并不会耗费一个线程,只会创建一个连接然后注册到selector上去罢了,一个selector线程不断的轮询所有的socket连接,发现有事件了就通知你,然后你就启动一个线程处理一个请求即可,但是这个处理的过程中,你还是要先读取数据,处理,再返回的,这是个同步的过程。

    所以NIO是同步非阻塞的。

    工作线程,从channel里读数据,是同步的,是工作线程自己去干这个事儿,卡在那儿,专门干读数据的这个活儿,数据没读完,你就卡死在这儿了;然后往channel里写数据,也是你自己去干这个事儿,卡死在这儿了,数据没写完,你就卡在这儿了.
    image.png

    3.3 AIO

    AIO是基于Proactor模型的,就是异步非阻塞模型。

    每个连接发送过来的请求,都会绑定一个buffer,然后通知操作系统去异步完成读,此时你的程序是会去干别的事儿的,等操作系统完成数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。

    然后你对这个数据处理一下,接着将结果往回写。

    写的时候也是给操作系统一个buffer,让操作系统自己获取数据去完成写操作,写完以后再回来通知你。

    工作线程,读取数据的时候,是说,你提供给操作系统一个buffer,空的,然后你就可以干别的事儿了,你就把读数据的事儿,交给操作系统去干,操作系统内核,读数据将数据放入buffer中,完事儿了,来回调你的一个接口,告诉你说,ok,buffer交给你了,这个数据我给你读好了

    写数据的时候也是一样的的,把放了数据的buffer交给操作系统的内核去处理,你就可以去干别的事儿了,操作系统完成了数据的写之后,级会来回调你,告诉你说,ok,哥儿们,你交给我的数据,我都给你写回到客户端去了

    3.4 同步阻塞、同步非阻塞、异步非阻塞

    但是这里为啥叫BIO是同步阻塞呢?这个其实不是针对网络编程模型来说的,是针对文件IO操作来说的,因为用BIO的流读写文件,是说你发起个IO请求直接hang死,必须等着搞完了这次IO才能返回

    BIO的这个同步阻塞,不是完全针对的网络通信模型去说的,针对的是磁盘文件的IO读写,FileInputStream,BIO,卡在那儿,直到你读写完成了才可以

    NIO为啥是同步非阻塞?就是说通过NIO的FileChannel发起个文件IO操作,其实发起之后就返回了,你可以干别的事儿,这就是非阻塞,但是接下来你还得不断的去轮询操作系统,看IO操作完事儿了没有。

    你呢也可以使用FileChannel这种NIO的模型,去读写磁盘文件,读数据,发起读数据的请求之后,你不是阻塞住的,你可以干别的事儿,但是你在干别的事儿的同时,还得来时不时的自己去轮询操作系统读数据的状态,看看人家读好了没有

    AIO为啥是异步非阻塞?就是说通过AIO发起个文件IO操作之后,你立马就返回可以干别的事儿了,接下来你也不用管了,操作系统自己干完了IO之后,告诉你说ok了。同步就是自己还得主动去轮询操作系统,异步就是操作系统反过来通知你。

    你也可以基于AIO的文件读写的api去读写磁盘文件,你发起一个文件读写的操作之后,交给操作系统,你就不去管他了,直到操作系统自己完成之后,会来回调你的一个接口,通知你说,ok,这个数据读好了,那个数据写完了

    3.5 BIO、NIO、AIO的demo代码

    上网找

     那么NIO和IO各适用的场景是什么呢?
      如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。
      而如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更合适。使用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作比较来权衡选择。