前言
之前打算总结一下Java的BIO(IO),AIO,NIO,最后一步步深入,发现Unix(Linux)的IO模型需要提前掌握,所以先总结一下Unix的IO模型。
概述
Java IO 与 Unix IO 的关系(非严格对应)
Unix网络编程中介绍了五种IO模型,分别是:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。
Java的IO模型与Unix的IO模型的对应关系如下所示(这个对应关系并不严格):
Java-IO模型
Unix-IO模型
BIO
阻塞式IO
NIO
IO多路复用
AIO
异步IO
数据的内核态和用户态
在进行IO模型讲解之前,先讲讲数据的两个状态:内核态和用户态。
我们将文件从磁盘加载到内存中。操作系统是怎么做的?
第一步:因为我们的所有程序,都是和操作系统的内核进行交互,所以文件首先是从磁盘加载到内核,这时候文件是处于内核态。
第二步:文件从内核再加载到内存中,应用程序此时才可以在内存中进行读写操作。这个时候的文件就是内核态。
所以Unix的五种IO模型的不同指出,就是这两个步骤的处理流程不同。
Unix五种IO模型分别介绍
阻塞式IO
这种模式很简单,系统给调用recvfrom函数之后,线程一直处于等待状态,一直等到:
第一步文件加载到内核态完成,第二步文件加载到用户态完成。
非阻塞IO
系统不停的通过recvfrom进行轮询,一直到第一步完成,然后在第二步阻塞式的将数据从内核态加载到用户态。
这里的非阻塞IO模式,主要是指第一步,加载数据到内核态,这个过程是非阻塞的,通过轮询来判断数据是否在内核准备好。
IO多路复用
系统首先通过select,阻塞式的查看内核数据是否准备完毕。
当内核数据加载完成之后,系统会调用recvfrom,将内核态的数据加载到用户态。
这里看起来第一步和第二步都是阻塞式操作,但是,select可以在极小代价的情况下,同时处理多个文件句柄(包括socket)。
信号驱动IO
第一步,相当于系统注册一个回调函数,当内核数据准备好了之后通知我。
此时系统可以做其他的事情,并不需要阻塞式的等待内核数据。
第二步,阻塞式调用recvfrom,将内核的数据加载到用户态。
异步IO
这个模型在理论上来说,是真正的异步模型,因为在以上四种模型中,在第二步:数据从内核态加载到用户态,都是同步操作。
而在该模型中,系统在加载文件的时候,只需要通过aio_read注册一个回调,当文件完成了内核态,用户态的加载之后,通知当前系统。
在这个过程中,系统不用等待,可以执行其他的运算任务。
汇总对比
这张图是对以上五种IO模型的汇总比较,总的来说,越靠后的模型在理论上来说就越高效。
前面的四种IO模型【阻塞IO、非阻塞IO、IO多路复用、信号驱动IO】,都属于同步IO,只有最后一种模型是真正的异步【异步IO】
系统调用介绍
1.Java的NIO老版本使用的是select模式,但后来改成了epoll,为什么?
因为select是轮询的模式,不停的检查文件句柄的状态。
epoll是callback的模式,当文件句柄准备好了之后,直接进行回调,更高效。
2.Java有真正的AIO模式吗?
在Windows系统下,通过IOCP实现。
在Linux系统下,没有,因为Linux系统下的AIO底层仍是epoll。
(个人猜测,这也是为什么Netty使用了NIO,而没有使用AIO)
他山之石
数据的内核态,用户态,这篇文章也有讲到,同时介绍了高效的数据传输方式:
zero-copy