一网打尽 Java NIO 面试题

 2022-08-02

1 NIO、BIO、AIO的区别

BIO :传统的网络通讯模型,就是BIO,同步阻塞IO

它其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。

这种方式的缺点:每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端

这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

BIO模型图:

202208022321035661.png

Acceptor:

传统的IO模型的网络服务的设计模式中有俩种比较经典的设计模式:一个是多线程, 一种是依靠线程池来进行处理。

如果是基于多线程的模式来的话,就是这样的模式,这种也是Acceptor线程模型。

202208022321047282.png

NIO:

NIO是一种同步非阻塞IO, 基于Reactor模型来实现的。

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

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

这里面优化 BIO 的核心就是,一个客户端并不是时时刻刻都有数据进行交互,没有必要死耗着一个线程不放,所以客户端选择了让线程歇一歇,只有客户端有相应的操作的时候才发起通知,创建一个线程来处理请求。

NIO:模型图

202208022321054713.png

Reactor模型:

202208022321061784.png

AIO

AIO:异步非阻塞IO,基于Proactor模型实现。

每个连接发送过来的请求,都会绑定一个Buffer,然后通知操作系统去完成异步的读,这个时间你就可以去做其他的事情

等到操作系统完成读之后,就会调用你的接口,给你操作系统异步读完的数据。这个时候你就可以拿到数据进行处理,将数据往回写

在往回写的过程,同样是给操作系统一个Buffer,让操作系统去完成写,写完了来通知你。

这两个过程都有buffer存在,数据都是通过buffer来完成读写。

这里面的主要的区别在于将数据写入的缓冲区后,就不去管它,剩下的去交给操作系统去完成。

操作系统写回数据也是一样,写到Buffer里面,写完后通知客户端来进行读取数据。

202208022321068665.png

总结:

一、同步阻塞I/O(BIO)

同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解

二、同步非阻塞I/O(NIO)

同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持

三、异步非阻塞I/O(AIO)

异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。

2 解释一下NIO的三大核心概念

缓存区Buffer概念缓存区实际上是一个数组,所有的数据都存储在缓存区,无论是想写入数据还是读取数据,都必须先将数据写入到缓冲区中。通道Channel概念通道可以类比为BIO中的流,所有的数据是存储在Buffer中的,但是需要通过通道Channel进行传输。通过Channel我们可以将数据从Buffer中读取出来或者写入Buffer。注意:Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。Buffer负责存储数据,Channel负责运输数据。选择器Selector概念Selector选择器用于同时监控多个 channel 的 IO事件,利用 Selector可使一个单独的线程管理多个 Channel。当Channel发生IO事件时,selector才会去处理事件,其余时间线程是非阻塞的。Selector 是非阻塞 IO 的核心。缓冲区类型ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer通道的类型FileChannel,对应文件IODatagramChannel,对应UDPSocketChannel,对应TCP的clientServerSocketChannel,对应TCP的server

3 BIO有什么缺点,为什么要用NIO?

BIO是同步阻塞的,当一个线程调用read() 或 write()时,该线程会被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。而且一个连接对应一个线程,当有多个线程请求读写时,需要启动很多线程,这就需要维护多线程,造成资源消耗大。虽然能用线程池解决多线程维护的问题,但是治标不治本,效率依然非常低。NIO是同步非阻塞的,一个连接就可以处理多个请求。NIO只有在连接有真正有读写事件时,才会进行读写,其余时间,线程是非阻塞的。而且不必为每一个连接都创建一个线程,更不用去维护多线程,避免了多线程之间的上下文切换,导致资源的浪费。

4 NIO是如何实现同步非阻塞的?

一个线程 Thread 使用一个选择器Selector监听多个通道 Channel 上的IO事件,从而让一个线程就可以处理多个IO事件。通过配置监听的通道Channel为非阻塞,那么当Channel上的IO事件还未到达时,线程会在select方法被挂起,让出CPU资源。直到监听到Channel有IO事件发生时,才会进行相应的响应和处理。Selector能够检测多个注册的通道上是否有IO事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。Selector只有在通道上有真正的IO事件发生时,才会进行相应的处理,这就不必为每个连接都创建一个线程,避免线程资源的浪费和多线程之间的上下文切换导致的开销。

5 BIO和NIO和AIO应用场景

1、BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择。2、NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。JDK1.4 开始支持。3AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

6 Buffer中对应的Position, Mark, Capacity,Limit都啥?#

capacity:缓冲区容量的大小,就是里面包含的数据大小。limit:对buffer缓冲区使用的一个限制,从这个index开始就不能读取数据了。position:代表着数组中可以开始读写的index, 不能大于limit。mark:是类似路标的东西,在某个position的时候,设置一下mark,此时就可以设置一个标记后续调用reset()方法可以把position复位到当时设置的那个mark上。去把position或limit调整为小于mark的值时,就丢弃这个mark如果使用的是Direct模式创建的Buffer的话,就会减少中间缓冲直接使用DirectorBuffer来进行数据的存储。

7 NIO组件之间的关系#

1.Selector选择器对应一个线程,一个线程对应多个Channel(连接)2.每一个Channel都对应一个buffer3.Buffer就是一个内存块,底层是一个数组4.程序切换到哪一个Channel是由事件决定的,Event就是一个重要的概念5.Selector会根据不同的事件,在各个通道上切换.6.数据的读取写入是是通过Buffer,通过flip方法进行切换实现双向操作,这与BIO有本质不同的,BIO是通过单向流input输入流或output输出流直接写入到通道中7.Channel也是双向的

8 select、poll、epoll 区别总结#

1、支持一个进程所能打开的最大连接数select单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。pollpoll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的epoll虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接2、FD剧增后带来的IO效率问题select因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。poll同上epoll因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。3、 消息传递方式select内核需要将消息传递到用户空间,都需要内核拷贝动作poll同上epollepoll通过内核和用户空间共享一块内存来实现的。

9 NIO模型,特别是其中的selector的职责#

这里出来一个新概念,selector,具体是一个什么样的东西?想想一个场景:在一个养鸡场,有这么一个人,每天的工作就是不停检查几个特殊的鸡笼,如果有鸡进来,有鸡出去,有鸡生蛋,有鸡生病等等,就把相应的情况记录下来,如果鸡场的负责人想知道情况,只需要询问那个人即可。在这里,这个人就相当Selector,每个鸡笼相当于一个SocketChannel,每个线程通过一个Selector可以管理多个SocketChannel。为了实现Selector管理多个SocketChannel,必须将具体的SocketChannel对象注册到Selector,并声明需要监听的事件(这样Selector才知道需要记录什么数据),一共有4种事件:1、connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT(8)2、accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT(16)3、read:读事件,对应值为SelectionKey.OP_READ(1)4、write:写事件,对应值为SelectionKey.OP_WRITE(4)这个很好理解,每次请求到达服务器,都是从connect开始,connect成功后,服务端开始准备accept,准备就绪,开始读数据,并处理,最后写回数据返回。所以,当SocketChannel有对应的事件发生时,Selector都可以观察到,并进行相应的处理。

10 大厂面试题扩展 NIO和Netty

NIO阐述 NIO原理?BIO/NIO/AIO有什么区别?有那些实现?讲讲NIO的原理与实现?NIO用到了哪个经典技术思想?JDK1.8中NIO有做什么优化了解多路复用机制 常见问题 同步阻塞、同步非阻塞、异步的区别?select、poll、eopll的区别?Linux网络IO模型哪些库或者框架用到NIO?redis的事件驱动多路复用底层实现;引申到NIO编程NIO解决了什么问题有了解过mina?NIO的核心是什么?(IO线程池) ,问IO包的设计模式(装饰器模式),为什么要这样设计?有没有更好的设计?NIO模型,特别是其中的selector的职责和实现原理select、poll 和 epoll 的区别NIO过程介绍,NIO怎么做到多路复用的NettyNetty 分布式任务调度怎么做?Netty 的优势在哪?有什么问题吗?NIO,Netty,网络协议,涉及到的OS交互Netty nio问题,问了流程Netty的 API gate 设计Netty线程模型(源码拷问)Netty的几种线程模型和架构