10分钟看懂,Java NIO 底层原理( 四 )


NIO的优点:每次发起的 IO 系统调用 , 在内核的等待数据过程中可以立即返回 。 用户线程不会阻塞 , 实时性较好 。
NIO的缺点:需要不断的重复发起IO系统调用 , 这种不断的轮询 , 将会不断地询问内核 , 这将占用大量的 CPU 时间 , 系统资源利用率较低 。
总之 , NIO模型在高并发场景下 , 也是不可用的 。 一般 Web 服务器不使用这种 IO 模型 。 一般很少直接使用这种模型 , 而是在其他IO模型中使用非阻塞IO这一特性 。 java的实际开发中 , 也不会涉及这种IO模型 。
再次说明 , Java NIO(New IO) 不是IO模型中的NIO模型 , 而是另外的一种模型 , 叫做IO多路复用模型( IO multiplexing ) 。
1.5. IO多路复用模型(I/O multiplexing)如何避免同步非阻塞NIO模型中轮询等待的问题呢?这就是IO多路复用模型 。
IO多路复用模型 , 就是通过一种新的系统调用 , 一个进程可以监视多个文件描述符 , 一旦某个描述符就绪(一般是内核缓冲区可读/可写) , 内核kernel能够通知程序进行相应的IO系统调用 。
目前支持IO多路复用的系统调用 , 有 select , epoll等等 。 select系统调用 , 是目前几乎在所有的操作系统上都有支持 , 具有良好跨平台特性 。 epoll是在linux 2.6内核中提出的 , 是select系统调用的linux增强版本 。
IO多路复用模型的基本原理就是select/epoll系统调用 , 单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接 , 当某个或者某些socket网络连接有数据到达了 , 就返回这些可以读写的连接 。 因此 , 好处也就显而易见了——通过一次select/epoll系统调用 , 就查询到到可以读写的一个甚至是成百上千的网络连接 。
举个栗子 。 发起一个多路复用IO的的read读操作系统调用 , 流程是这个样子:
10分钟看懂,Java NIO 底层原理文章插图
在这种模式中 , 首先不是进行read系统调动 , 而是进行select/epoll系统调用 。 当然 , 这里有一个前提 , 需要将目标网络连接 , 提前注册到select/epoll的可查询socket列表中 。 然后 , 才可以开启整个的IO多路复用模型的读流程 。
(1)进行select/epoll系统调用 , 查询可以读的连接 。 kernel会查询所有select的可查询socket列表 , 当任何一个socket中的数据准备好了 , select就会返回 。
当用户进程调用了select , 那么整个线程会被block(阻塞掉) 。
(2)用户线程获得了目标连接后 , 发起read系统调用 , 用户线程阻塞 。 内核开始复制数据 。 它就会将数据从kernel内核缓冲区 , 拷贝到用户缓冲区(用户内存) , 然后kernel返回结果 。
(3)用户线程才解除block的状态 , 用户线程终于真正读取到数据 , 继续执行 。
多路复用IO的特点:
IO多路复用模型 , 建立在操作系统kernel内核能够提供的多路分离系统调用select/epoll基础之上的 。 多路复用IO需要用到两个系统调用(system call) ,一个select/epoll查询调用 , 一个是IO的读取调用 。
和NIO模型相似 , 多路复用IO需要轮询 。 负责select/epoll查询调用的线程 , 需要不断的进行select/epoll轮询 , 查找出可以进行IO操作的连接 。
另外 , 多路复用IO模型与前面的NIO模型 , 是有关系的 。 对于每一个可以查询的socket , 一般都设置成为non-blocking模型 。 只是这一点 , 对于用户程序是透明的(不感知) 。
多路复用IO的优点:
用select/epoll的优势在于 , 它可以同时处理成千上万个连接(connection) 。 与一条线程维护一个连接相比 , I/O多路复用技术的最大优势是:系统不必创建线程 , 也不必维护这些线程 , 从而大大减小了系统的开销 。
Java的NIO(new IO)技术 , 使用的就是IO多路复用模型 。 在linux系统上 , 使用的是epoll系统调用 。
多路复用IO的缺点:
本质上 , select/epoll系统调用 , 属于同步IO , 也是阻塞IO 。 都需要在读写事件就绪后 , 自己负责进行读写 , 也就是说这个读写过程是阻塞的 。
如何充分的解除线程的阻塞呢?那就是异步IO模型 。
1.6. 异步IO模型(asynchronous IO)如何进一步提升效率 , 解除最后一点阻塞呢?这就是异步IO模型 , 全称asynchronous I/O , 简称为AIO 。
AIO的基本流程是:用户线程通过系统调用 , 告知kernel内核启动某个IO操作 , 用户线程返回 。 kernel内核在整个IO操作(包括数据准备、数据复制)完成后 , 通知用户程序 , 用户执行后续的业务操作 。
kernel的数据准备是将数据从网络物理设备(网卡)读取到内核缓冲区;kernel的数据复制是将数据从内核缓冲区拷贝到用户程序空间的缓冲区 。