什么是NIO
Java NIO (New IO)是 Java 的另一个 IO API (来自 java1.4) ,意味着可以替代标准的 Java IO API和 Java Networking API。提供了一种与标准 IO API 不同的 IO 工作方式。注意:Java的NIO只指IO API,阻塞和不阻塞是IO的模型。
还有人称NIO为无阻塞IO,非阻塞IO,但这么说并不严格。因为基本的IO操作API(比如文件IO、文件通道)还是一个阻塞模型。只有网络输入输出应用编程接口可以使用非阻塞模型(配置阻塞(假))。
Java NIO中的联网IO API支持无阻塞IO模型,实现IO复用。对于服务器来说,它可以用更少的线程支持更多的并发,大大提高了性能。
NIO中的阻塞和非阻塞
阻塞和非阻塞都是从线程的角度出发,这里指的是线程状态。
阻塞
做IO读写时,线程被阻塞。此时,cpu控制权将被放弃,cpu资源将不会被占用。
什么?不占用CPU资源?是不是说分块模式更好?
答案是否定的,阻塞状态虽然不会占用CPU,但是会造成线程切换,而且线程切换的时候会有一个上下文保存和转换的过程,需要CPU调度,是一个非常昂贵的操作。
Java NIO中的基本IO API(非联网IO API)还是一种阻塞方式,但是从面向流编程到缓冲区都在使用,本质上和BIO没什么区别。
无阻塞
非阻塞是指在执行IO操作时,如果设备没有准备好(比如套接字没有收到数据),操作会直接返回结果,不会导致当前线程进入阻塞状态。
这样做的好处是,当数据没有准备好时,用户可以决定做什么。线程可以在没有数据的情况下执行其他操作。
网络应用编程接口可以配置为非阻塞模型通道。configureblocking (false),与选择器结合实现复用功能。简单来说,一个选择器监听多个套接字io(对于unix系统,套接字也是一个fd和io),可以在一个线程中支持多个连接。当然,在实际的服务器开发中,即使是NIO模型,有些程序也不会只使用一个线程;但是相对于传统的Blocking IO方式,所需线程数会大大减少。(redis使用IO复用技术,只有一个线程监听socket io)
免疫球蛋白源的淀粉样蛋白
AIO是在Java 1.7之后引入的包,Java 1.7是NIO的升级版本。它增加了异步和非阻塞IO操作模式,所以人们称之为AIO(异步IO)。异步IO是基于事件和回调机制实现的,即应用操作后直接返回,不在那里阻塞。后台处理完成后,操作系统会执行回调,通知相应的线程进行后续操作。
多路技术
在I/O编程过程中,当需要同时处理多个客户端请求时,可以采用多线程或I/O复用技术进行处理。I/O复用技术将多个I/O块复用到同一个Select块,使得系统可以在单线程的情况下同时处理多个客户端请求。与传统的多线程/多进程模型相比,I/O复用最大的优点是系统不需要创建新的额外的进程或线程,也不需要维护这些线程和进程的运行,从而减少了系统的维护工作量,节约了系统资源。输入输出复用的主要应用场景如下:
服务器需要同时处理多个处于监听状态或者多个连接状态的Socket服务器需要同时处理多种网络协议的Socket目前支持I/O复用的系统调用是select/pselect/poll/epoll。
选择/epoll
选择
选择的实现很简单。如果程序同时监控如下图所示的sock1、sock2、sock3三个套接字,在调用select后,操作系统会将进程a分别添加到这三个套接字的等待队列中。
当任何一个套接字接收到数据时,中断程序都会调用这个过程。下图是sock2接收数据的处理流程。
唤醒一个进程意味着从所有等待队列中移除该进程,并将其添加到工作队列中。如下图所示。
通过这些步骤,当进程A醒来时,它知道至少有一个套接字接收到了数据。程序只需要遍历一次套接字列表就可以得到准备好的套接字。
这种简单的方式是有效的,在几乎所有的操作系统中都有相应的实现。
然而,简单的方法往往有缺点,主要是:
第一,每次调用select,都需要将进程添加到所有监控套接字的等待队列中,每次醒来都需要将其从每个队列中移除。涉及到两次遍历,每次都要把整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,为了效率才规定了select的最大监控次数,默认只能监控1024个socket。
第二,进程被唤醒后,程序不知道哪个套接字接收数据,需要重新遍历。
那么,有什么方法可以减少遍历呢?有没有办法保存一个现成的套接字?这两个问题都要通过epoll技术来解决。
补充说明:本节只解释了select的一种情形。当程序调用select时,内核会先遍历一遍socket,如果有一个以上的socket接收缓冲区有数据,那么select直接返回,不会阻塞。这也是为什么select的返回值有可能大于1的原因之一。如果没有socket有数据,进程才会阻塞。select效率低的原因之一是结合了“维护等待队列”和“阻塞进程”两个步骤。如下图所示,每次调用选择都需要这两个操作。然而,在大多数应用场景中,要监控的套接字是相对固定的,不需要每次都进行修改。Epoll将这两个操作分开,首先使用epoll_ctl维护等待队列,然后调用epoll_wait阻塞进程。显然可以提高效率。
select效率低的另一个原因是程序不知道哪个套接字接收数据,只能逐个遍历。如果内核维护一个“就绪列表”,并引用接收数据的套接字,就可以避免遍历。如下图所示,计算机中有三个socket,接收数据的sock2和sock3被rdlist引用。当进程被唤醒时,您可以通过获取rdlist的内容来知道哪个套接字接收数据。
使用
Epall是select出现n多年后发明的,是select和poll的增强版。Epoll通过以下措施提高效率。
原则:
创建epoll对象
如下图所示,当一个进程调用epoll_create方法时,内核会创建一个eventpoll对象(即程序中epfd表示的对象)。Eventpoll对象也是文件系统的成员,和socket一样,它也有一个等待队列。
有必要创建一个表示epoll的eventpoll对象,因为内核需要维护诸如Ready List之类的数据,这些数据可以用作eventpoll的成员。
维护观察列表
创建epoll对象后,可以使用epoll_ctl来添加或删除您想要监听的套接字。以添加套接字为例,如下图所示,如果通过epoll_ctl添加sock1、sock2、sock3的监控,那么内核会在这三个套接字的等待队列中添加eventpoll。
当套接字接收到数据时,中断程序将操作eventpoll对象,而不是直接操作进程。
接收数据
当套接字接收到数据时,中断程序会将套接字引用添加到eventpoll的“就绪列表”中。下图显示sock2和sock3收到数据后,中断程序让rdlist引用这两个套接字。
Eventpoll对象相当于套接字和进程之间的中介。socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程的状态。
当程序执行到epoll_wait时,如果rdlist已经引用socket,则epoll_wait直接返回,如果rdlist为空,则阻塞进程。
阻断和唤醒过程
假设进程A和进程B在计算机中运行,进程A在某个时刻运行epoll_wait语句。如下图所示,内核会将进程A放入eventpoll的等待队列,阻塞进程。
当套接字接收到数据时,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。由于rdlist,进程a可以知道哪个套接字发生了变化。
指
Netty权威指南https://zhuanlan.zhihu.com/p/64138532http://tutorials.jenkov.com/java-nio/index.html空无
https://segmentfault.com/a/1190000019814737
1.《nio 一篇文章带你彻底搞懂NIO》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《nio 一篇文章带你彻底搞懂NIO》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/1005535.html