1,名词解释linux
IO:Input/Output表示数据的交换,广义上的IO包括文件IO、网络IO、其余IO。本篇只讨论网络IO,也就是socket IO。segmentfault
IO:默认表示传统的Blocking IO(阻塞式IO)。
NIO:JDK1.4推出,Sun官方叫作NewIO,民间叫作Non-blocking IO(非阻塞IO)。
AIO:JDK1.7推出,Asynchronous IO(异步IO),也叫NIO2.0。安全
2,为何分为这几种IO?网络
JVM虚拟机运行在操做系统上,JVM的native方法至关因而对操做系统API的封装(抽象)。因此,首先得理解操做系统支持的IO。
Linux操做系统支持的网络IO有5种形式:异步
因为signal driven IO在实际中并不经常使用,只介绍其余4种IO Model。介绍以前,得须要了解一些概念。socket
2.1,网络数据是如何从网卡内存转移到用户线程中的?async
用户线程发送读取命令到操做系统,操做系统将数据从网卡中读取到内核空间中,再从内核空间转移到用户线程中。函数
2.2,什么是内核空间,什么是用户线程空间?操作系统
为了保证用户进程不能直接操做内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对32位的linux操做系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。线程
一句话总结:为了安全操做,网卡中的数据须要先读到内核空间中,再转移到用户空间中。
最重要的是要理解:分为两步操做来完成一个读的操做。
2.3,什么是线程阻塞?
用户线程发起请求,告诉操做系统,须要读取数据。操做系统去读取数据放到内核空间中,再从内核空间写到用户线程中,这都是须要时间的。用户线程只能等待数据就绪以后才能执行,那这段时间内总不能占着CPU使用权不放吧?因而,用户线程阻塞等待获取到数据(没法继续执行后续代码),会主动释放掉CPU执行权。
2.4.1,什么是阻塞 I/O(blocking IO)
在linux中,默认状况下全部的socket都是blocking,一个典型的读操做流程大概是这样:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:将数据从网卡读到内核中。因为网络的接收是须要时间的(网络用户的数据并无彻底传输完),从网卡读到内核也是须要时间的,因此第一阶段阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,而后kernel返回结果,用户进程才解除block的状态,从新运行起来。
blocking IO的特色就是在IO执行的两个阶段时,用户线程都处于阻塞状态。
2.4.2,什么是非阻塞 I/O(non-blocking IO)
linux下,能够经过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操做时,流程是这个样子:
当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是一个error时,它就知道数据尚未准备好,因而它能够再次发送read操做。一旦kernel中的数据准备好了,而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存,而后返回。
nonblocking IO的特色是用户进程须要不断的主动询问kernel数据好了没有。
2.4.3,什么是I/O 多路复用( IO multiplexing) 在non-blocking IO中,用户线程不断的主动询问一个数据有没有准备好,比较浪费cpu。因而,由一个线程来主动轮询多个数据是否准备好,每次均可以返回多个标识符,表示哪些数据内核空间已经准备好了,用户能够读取了。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。
I/O 多路复用的特色是经过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就能够返回。
用select的优点在于它能够同时处理多个connection。
2.4.4,什么是异步 I/O(asynchronous IO)
inux下的asynchronous IO其实用得不多。先看一下它的流程:
用户进程发起read操做以后,马上就能够开始去作其它的事。而另外一方面,从kernel的角度,当它受到一个asynchronous read以后,首先它会马上返回,因此不会对用户进程产生任何block。而后,kernel会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。
2.5,几种IO的比较
各个IO Model的比较如图所示:
2.6,同步IO和异步IO的区别
在说明synchronous IO和asynchronous IO的区别以前,须要先给出二者的定义。POSIX的定义是这样子的:
这里的“I/O operation”包括上文提到的两步:1,内核空间数据的准备;2,将数据从内核空间拷贝到用户空间。
同步IO和异步IO二者的区别就在于同步IO作”IO operation”的时候会将线程阻塞。按照这个定义,以前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。只有最后一种asynchronous I/O是异步IO。
=======================================分割线==================================
举个例子说明。
假设你有个朋友A,托你帮他在花店定作一束花,再帮他送给B。
blocking IO:你在花店里一直等待花作好,而后亲自送给B,等待B签收。这期间你不能干别的事情。
non-blocking IO:你不停给花店打电话询问花是否作好了,确认作好了以后你去拿花而且亲自送给B,等待B签收。
每次打电话询问A的一束花,效率过低。因而你又开始帮朋友A1,A2...An在花店定作花而且送给B1,B2...Bn。
这样每打一次电话,都有可能收到许多花准备好的通知。(不必定有前后顺序,毕竟定作的花也有难易程度)
IO multiplexing:每次打电话给花店能够询问许多花是否准备好,花店给你的回复是其中某些花准备好了。因而你把这些已经准备好的花拿到而且一个一个的亲自送给目标B,等待B签收后送下一个目标B。
每次都是你亲自送花,这样效率过低了。某天花店推出了一项业务,能够帮助顾客送花,这样就不用你亲自送了。
asynchronous IO:你告诉花店定作的花,而且告诉花店这束花送到目标B的地址,让花店去送给B。而后你随意干什么都行。等花店把花送达B的时候会给你发个通知。
朋友A就是客户端,目标B就是用户线程,你就是Java的IO底层程序。你雇佣的人就是线程池。
参考资料:
1,Linux IO模式及 select、poll、epoll详解
2,IO模型解惑