一篇文章带你完全搞懂NIO

本篇文章目的在于基本概念和原理的解释,不会贴过多的使用代码。html

什么是NIO

Java NIO (New IO)是 Java 的另外一个 IO API (来自 java1.4) ,意味着能够替代标准的 Java IO API和 Java Networking API。 提供了一种与标准 IO API 不一样的 IO 工做方式。

注意:Java的NIO只是说IO API,阻塞非阻塞才是IO的模型。java

也有人称NIO为No-Blocking IO,非阻塞IO,可是这么说并不严谨。由于对于基础的IO操做API(好比文件IO,FileChannel),仍是阻塞的模型。只有对Networking IO API才可使用非阻塞的模型(configureBlocking(false))。redis

Java NIO中的Networking IO API,支持非阻塞IO模型,还实现了IO多路复用(IO Multiplexing)。对于服务端来讲,能够用更少的线程支持更多的并发,大幅度提高了性能。编程

NIO中的阻塞与非阻塞

阻塞与非阻塞是从线程的角度出发的,这里指的是线程状态。服务器

阻塞

当进行IO读写时,线程是阻塞的状态。此时会让出cpu控制权,不会占用cpu资源。网络

什么?不占用CPU资源?那是否是表明阻塞模型更好呢?多线程

答案是并非,虽然阻塞状态不会占用CPU,可是会发生线程的切换,线程切换时会有上下文保存转换的过程,须要CPU调度,是一个很昂贵的操做。并发

Java NIO中的基础IO API(非Networking IO API)仍是阻塞的方式,只是使用方式从面向流(stream)编程面向块(buffer)了,和BIO本质上并无什么区别。异步

非阻塞

非阻塞是指在进行IO操做的时候,若是设备还未准备好(好比socket尚未收到数据),操做会直接返回结果,不会让当前线程进入阻塞状态。socket

这样的优势是,使用者能够自行决定在数据未准备好时的操做。线程能够在没有数据期间去执行其余操做。
Networking API能够配置为非阻塞模型Channel.configureBlocking(false),配合Selector来实现多路复用功能。简单的说就是一个Selector监听多个socket io(对于unix系统来讲,socket也是一个fd,也属于io),能够在一个线程中支持多个链接。固然在实际服务器开发时,就算是NIO模型,有些程序也不会只使用一个线程;但相比传统的Blocking IO方式来讲,须要的线程数量也会大大减小了。(redis中就是使用了IO多路复用技术,而且只有一个线程监听socket io)

AIO

AIO 是 Java 1.7 以后引入的包,是 NIO 的升级版本,新增了提异步非阻塞的 IO 操做方式,因此人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操做以后会直接返回,不会堵塞在那里,当后台处理完成,操做系统会执行回调通知相应的线程进行后续的操做。

多路复用

在I/O编程过程当中,当须要同时处理多个客户端请求时,能够利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术经过把多个I/O的阻塞复用到同一个Select的阻塞上,从而使得系统在单线程的状况下能够同时处理多个客户端请求。与传统的多线程/多进程模型相比,I/O多路复用的最大优点是系统开销小,系统不须要建立新的额外进程或者线程,也不须要维护这些线程和进程的运行,下降了系统的维护工做量,节省了系统的资源,I/O多路复用的主要应用场景以下:

  • 服务器须要同时处理多个处于监听状态或者多个链接状态的Socket
  • 服务器须要同时处理多种网络协议的Socket

目前支持I/O多路复用的系统调用又select/pselect/poll/epoll。

select/epoll

select/epoll的介绍摘自https://zhuanlan.zhihu.com/p/...

select

select的实现思路很直接。假如程序同时监视以下图的sock一、sock2和sock3三个socket,那么在调用select以后,操做系统把进程A分别加入这三个socket的等待队列中。

v2-0cccb4976f8f2c2f8107f2b3a5bc46b3_hd.jpg

当任何一个socket收到数据后,中断程序将唤起进程。下图展现了sock2接收到了数据的处理流程。

v2-85dba5430f3c439e4647ea4d97ba54fc_hd.jpg

所谓唤起进程,就是将进程从全部的等待队列中移除,加入到工做队列里面。以下图所示。

v2-a86b203b8d955466fff34211d965d9eb_hd.jpg

经由这些步骤,当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就能够获得就绪的socket。

这种简单方式行之有效,在几乎全部操做系统都有对应的实现。

可是简单的方法每每有缺点,主要是:

其一,每次调用select都须要将进程加入到全部监视socket的等待队列,每次唤醒都须要从每一个队列中移除。这里涉及了两次遍历,并且每次都要将整个fds列表传递给内核,有必定的开销。正是由于遍历操做开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。

其二,进程被唤醒后,程序并不知道哪些socket收到数据,还须要遍历一次。

那么,有没有减小遍历的方法?有没有保存就绪socket的方法?这两个问题即是epoll技术要解决的。

补充说明: 本节只解释了select的一种情形。当程序调用select时,内核会先遍历一遍socket,若是有一个以上的socket接收缓冲区有数据,那么select直接返回,不会阻塞。这也是为何select的返回值有可能大于1的缘由之一。若是没有socket有数据,进程才会阻塞。

select低效的缘由之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。以下图所示,每次调用select都须要这两步操做,然而大多数应用场景中,须要监视的socket相对固定,并不须要每次都修改。epoll将这两个操做分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能获得提高。

v2-5ce040484bbe61df5b484730c4cf56cd_hd.jpg

select低效的另外一个缘由在于程序不知道哪些socket收到数据,只能一个个遍历。若是内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。以下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就可以知道哪些socket收到数据。

v2-5c552b74772d8dbc7287864999e32c4f_hd.jpg

epoll

epoll是在select出现N多年后才被发明的,是select和poll的加强版本。epoll经过如下一些措施来改进效率。

原理:

建立epoll对象

以下图所示,当某个进程调用epoll_create方法时,内核会建立一个eventpoll对象(也就是程序中epfd所表明的对象)。eventpoll对象也是文件系统中的一员,和socket同样,它也会有等待队列。

v2-e3467895734a9d97f0af3c7bf875aaeb_hd.jpg

建立一个表明该epoll的eventpoll对象是必须的,由于内核要维护“就绪列表”等数据,“就绪列表”能够做为eventpoll的成员。

维护监视列表

建立epoll对象后,能够用epoll_ctl添加或删除所要监听的socket。以添加socket为例,以下图,若是经过epoll_ctl添加sock一、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。

v2-b49bb08a6a1b7159073b71c4d6591185_hd.jpg

当socket收到数据后,中断程序会操做eventpoll对象,而不是直接操做进程。

接收数据

当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。以下图展现的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。

v2-18b89b221d5db3b5456ab6a0f6dc5784_hd.jpg

eventpoll对象至关因而socket和进程之间的中介,socket的数据接收并不直接影响进程,而是经过改变eventpoll的就绪列表来改变进程状态。

当程序执行到epoll_wait时,若是rdlist已经引用了socket,那么epoll_wait直接返回,若是rdlist为空,阻塞进程。

阻塞和唤醒进程

假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。以下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。
v2-90632d0dc3ded7f91379b848ab53974c_hd.jpg

当socket接收到数据,中断程序一方面修改rdlist,另外一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(以下图)。也由于rdlist的存在,进程A能够知道哪些socket发生了变化。

v2-40bd5825e27cf49b7fd9a59dfcbe4d6f_hd.jpg

参考

相关文章
相关标签/搜索