在Java中,有三种IO模型: BIO,NIO,AIO。介绍这三种IO模型以前,须要介绍一下同步,异步与阻塞,非阻塞的概念,而后再从Java和Linux OS的角度去分析BIO,NIO和AIOjava
同步linux
同步就是发起一个调用后,被调用者未处理完请求以前,调用不返回。编程
通俗的例子描述同步就像:缓存
你打电话问书店老板有没有《葵花宝典》这本书的时候,若是是同步机制,书店老板会说,你稍等,”我查一下",而后开始查啊查,等查好了(多是5秒,也多是一天)告诉你结果(返回结果)。服务器
异步网络
异步就是发起一个调用后,马上获得被调用者的回应表示已接收到请求,可是被调用者并无返回结果,此时咱们能够处理其余的请求,被调用者一般依靠事件,回调等机制来通知调用者其返回结果。框架
通俗的例子描述异步就像:异步
而异步机制,书店老板直接告诉你我查一下啊,查好了打电话给你,而后直接挂电话了(不返回结果)。而后查好了,他会主动打电话给你。在这里老板经过“回电”这种方式来回调函数
此处参照知乎上关于此问题的回答:www.zhihu.com/question/19…post
再次总结一下同步与异步:
同步与异步最大的区别就是被调用方的执行方式和返回时机,同步指的是被调用方作完事情以后再返回,异步指的是被调用方先返回,而后再作事情,作完以后再想办法通知调用方
阻塞
阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,没法从事其余任务,只有当条件就绪才能继续。
非阻塞
非阻塞就是发起一个请求,调用者不用一直等着结果返回,能够先去干其余事情。
仍是上面买书的例子:
你打电话问书店老板有没有《葵花宝典》这本书,你若是是阻塞式调用,你会一直把本身“挂起”,直到获得这本书有没有的结果,若是是非阻塞式调用,你无论老板有没有告诉你,你本身先一边去玩了, 固然你也要偶尔过几分钟check一下老板有没有返回结果。
阻塞和同步不是一回事,同步,异步与阻塞,非阻塞针对的对象是不同的,阻塞,非阻塞是说的调用者,同步,异步说的是被调用者
BIO(Blocking I/O):BIO也就是传统的同步阻塞IO模型,对应Java.io包,它提供了不少IO功能,好比输入输出流,对文件进行操做。在网络编程(Socket通讯)中也一样进行IO操做。
NIO(New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象
AIO: AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型
上面简单介绍了Java中的三种IO模型,三种模型提供的与IO有关的API,在文件处理时,底层其实是依赖操做系统层面的IO操做实现的,好比在Linux 2.6之后,Java中的NIO和AIO都是经过 epoll来实现的,关于epoll等概念后面也会阐述。
而实际上在Linux(Unix)操做系统中,共有五种 IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型,而4种都是同步的,只有最后一种是异步的。下面的分析主要参考了《UNIX网络编程 卷1:套接字联网API(第3版)中的介绍。
一个输入操做一般包括两个不一样的阶段:
对于一个套接字上的输入操做,第一步一般涉及等待数据从网络中到达,当所等待分组到达时,它被复制到内核中的某个缓冲区,第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
从上图能够看出,应用进程经过 系统调用 recvfrom
去接收数据,而因为内核数据没有准备好,应用进程就会阻塞,直到内核准备好数据并将其从内核复制到应用进程的缓冲区中或者发生错误才返回。最多见的错误就是系统调用被信号中断。进程从调用recvfrom开始到它返回的整段时间内是被阻塞的。
Linux下的阻塞式I/O模型就对应了Java下的BIO模型,BIO的底层实现是调用操做系统的API去执行的,也就是调用操做系统的Socket套接字。
应用进程经过系统调用 recvfrom
不断的去和内核交互,直到内核数据报准备好,而若是内核无数据准备好,转而当即返回一个 EWOULDBLOCK
的错误,过一段时间再次发送 recvfrom
请求,在此期间进程能够作其余事情,不用一直等待,这就是非阻塞。
当一个应用进程循环调用 recvfrom
时,咱们称之为轮询(polling),应用进程持续轮询内核,以查看某个操做是否就绪。Java的NIO映射到Linux操做系统就是如上图所示的非阻塞I/O模型
IO多路复用使用select/poll/epoll
函数,多个进程的IO均可以注册在同一个 select
上,当用户进程调用该 select
时,select
去监听全部注册好的IO,若是全部被监听的IO须要的数据都没有准备好,那么 select
调用进程会被阻塞,只要任意一个IO的数据报套接字变为可读,即数据报已经准备好,select
就返回套接字可读这一条件,而后调用 recvfrom
把所读数据报复制到应用进程缓冲区。
强调一点就是,IO多路复用模型并无涉及到非阻塞,进程在发出select
后,要一直阻塞等待其监听的全部IO操做至少有一个数据准备好才返回,强调阻塞状态,不存在非阻塞。
而在 Java NIO中也能够实现多路复用,主要是利用多路复用器 Selector,与这里的 select
函数类型,Selector会不断轮询注册在其上的通道Channel,若是有某一个Channel上面发生读或写事件,这个Channel处于就绪状态,就会被Selector轮询出来。关于Java NIO实现多路复用更多的介绍请查询相关文章。
I/O 多路复用的主要应用场景以下:
目前支持I/O 多路复用的系统调用函数有 select,pselect,poll,epoll
。在Linux 网络编程中,很长一段时间都使用select
作轮询和网络事件通知。然而由于select的一些固有缺陷致使它的应用受到了很大的限制,好比select 单个进程打开的最大句柄数是有限的。最终在 Linux 2.6 选择epoll 替代了select,Java NIO和AIO底层就是用epoll。更多关于这些系统调用的介绍与使用,请参阅 《UNIX网络编程 卷1:套接字联网API(第3版)
应用进程预先向内核安装一个信号处理函数,而后当即返回,进程继续工做,不阻塞,当数据报准备好读取时,内核就为该进程产生一个信号通知进程,而后进程再调用recvfrom读取数据报。
信号驱动式IO在数据准备阶段是异步的,当内核中有数据报准备后再通知进程,可是在调用 recvfrom操做进行数据拷贝时是同步的,因此整体来讲,整个IO过程不能是异步的。
应用进程调用aio_read
函数,给内核传递描述符,缓存区指针,缓存区大小和文件偏移,并告诉内核当整个操做完成时如何通知进程,而后该系统调用当即返回,并且在等待I/O完成期间,咱们的进程不被阻塞,进程能够去干其余事情,而后内核开始等待数据准备,数据准备好之后再拷贝数据到进程缓冲区,最后通知整个IO操做已完成。
Java的AIO提供了异步通道API,其操做系统底层实现就是这个异步I/O模型
主要区别在于: 信号驱动式I/O是由内核通知咱们什么时候去启动一个I/O操做,而异步I/O模型是由内核通知咱们I/O操做什么时候完成。
由上图能够再次看出,IO操做主要分为两个阶段:
前4种IO模型都是同步IO模型,为何说都是同步的,由于它们在第二步数据拷贝阶段都是阻塞的,这会致使整个请求进程存在阻塞的状况,因此是同步的,而异步IO模型不会致使请求进程阻塞。
上面简要阐述了BIO,NIO,AIO以及Linux下的5种IO模型,对于IO模型更加详细的介绍参考《UNIX网络编程 卷1:套接字联网API(第3版)