NIo、Bio、aio、 的原理及区别与应用场景

在高性能的IO体系设计中,有几个名词概念经常会使咱们感到疑惑不解。具体以下: 
html

序号 问题
1 什么是同步?
2 什么是异步?
3 什么是阻塞?
4 什么是非阻塞?
5 什么是同步阻塞?
6 什么是同步非阻塞?
7 什么是异步阻塞?
8 什么是异步非阻塞?


散仙不才,在查了一部分资料后,愿试着以通俗易懂的方式解释下这几个名词。若有不足之处,还望告知。 



在弄清楚上面的几个问题以前,咱们首先得明白什么是同步,异步,阻塞,非阻塞,只有这几个单个概念理解清楚了,而后在组合理解起来,就相对比较容易了。 

1,同步和异步是针对应用程序和内核的交互而言的。 同步/异步是在时间上强调处理事情的结果/机会成本的两种处理策略;强调结果意味着对结果的迫不急待,不过结果是正确的仍是错误的,反正你要当即给我一个结果响应;强调时间机会成本意味着对等待结果浪费的时间极其难接受,而对结果并非那么急切,暂时无论结果(让处理方处理完主动通知结果/本身空闲的时候主动去获取结果)而去处理其余事情

2,阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操做的就绪状态来采起的不一样方式,说白了是一种读取或者写入操做函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会当即返回一个状态值。


3,同步/异步是宏观上(进程间通信,一般表现为网络IO的处理上),阻塞/非阻塞是微观上(进程内数据传输,一般表现为对本地IO的处理上);阻塞和非阻塞是同步/异步的表现形式

由上描述基本能够总结一句简短的话,同步和异步是目的,阻塞和非阻塞是实现方式。 

java

编号 名词 解释 举例
1 同步 指的是用户进程触发IO操做并等待或者轮询的去查看IO操做是否就绪 本身上街买衣服,本身亲自干这件事,别的事干不了。
2 异步 异步是指用户进程触发IO操做之后便开始作本身的事情,而当IO操做已经完成的时候会获得IO完成的通知(异步的特色就是通知) 告诉朋友本身合适衣服的尺寸,大小,颜色,让朋友委托去卖,而后本身能够去干别的事。(使用异步IO时,Java将IO读写委托给OS处理,须要将数据缓冲区地址和大小传给OS)
3 阻塞 所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 若是当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 去公交站充值,发现这个时候,充值员不在(可能上厕所去了),而后咱们就在这里等待,一直等到充值员回来为止。(固然现实社会,可不是这样,可是在计算机里确实如此。)
4 非阻塞 非阻塞状态下, 若是没有东西可读, 或者不可写, 读写函数立刻返回, 而不会等待, 银行里取款办业务时,领取一张小票,领取完后咱们本身能够玩玩手机,或者与别人聊聊天,当轮咱们时,银行的喇叭会通知,这时候咱们就能够去了。





下面咱们再来理解组合方式的IO类型,就好理解多了。 

同步阻塞IO(JAVA BIO): 
    同步并阻塞,服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就须要启动一个线程进行处理,若是这个链接不作任何事情会形成没必要要的线程开销,固然能够经过线程池机制改善。 

同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。用户进程也须要时不时的询问IO操做是否就绪,这就要求用户进程不停的去询问。 

异步阻塞IO(Java NIO):  
   此种方式下是指应用发起一个IO操做之后,不等待内核IO操做的完成,等内核完成IO操做之后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为何说是阻塞的呢?由于此时是经过select系统调用来完成的,而select函数自己的实现方式是阻塞的,而采用select函数有个好处就是它能够同时监听多个文件句柄(若是从UNP的角度看,select属于同步操做。由于select以后,进程还须要读写数据),从而提升系统的并发性!  


(Java AIO(NIO.2))异步非阻塞IO:  
   在此种模式下,用户进程只须要发起一个IO操做而后当即返回,等IO操做真正的完成之后,应用程序会获得IO操做完成的通知,此时用户进程只须要对数据进行处理就行了,不须要进行实际的IO读写操做,由于真正的IO读取或者写入操做已经由内核完成了。    



BIO、NIO、AIO适用场景分析: 

    BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的惟一选择,但程序直观简单易理解。 

    NIO方式适用于链接数目多且链接比较短(轻操做)的架构,好比聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 

    AIO方式使用于链接数目多且链接比较长(重操做)的架构,好比相册服务器,充分调用OS参与并发操做,编程比较复杂,JDK7开始支持。 

搞清楚了以上概念之后,咱们再回过头来看看,Reactor模式和Proactor模式。  
  
(其实阻塞与非阻塞均可以理解为同步范畴下才有的概念,对于异步,就不会再去分阻塞非阻塞。对于用户进程,接到异步通知后,就直接操做进程用户态空间里的数据好了。)  
  
首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。咱们分别以读操做和写操做为例来看看Reactor中的具体步骤:  
读取操做:  
1. 应用程序注册读就绪事件和相关联的事件处理器  
  
2. 事件分离器等待事件的发生  
  
3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器  
  
4. 事件处理器首先执行实际的读取操做,而后根据读取到的内容进行进一步的处理  
  
写入操做相似于读取操做,只不过第一步注册的是写就绪事件。  
   
  
下面咱们来看看Proactor模式中读取操做和写入操做的过程:  
读取操做:  
1. 应用程序初始化一个异步读取操做,而后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。  
  
2. 事件分离器等待读取操做完成事件  
  
3. 在事件分离器等待读取操做完成的时候,操做系统调用内核线程完成读取操做(异步IO都是操做系统负责将数据读写到应用传递进来的缓冲区供应用程序操做,操做系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序须要传递缓存区。  
  
4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不须要进行实际的读取操做。  
  
Proactor中写入操做和读取操做,只不过感兴趣的事件是写入完成事件。  
  
从上面能够看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操做是有谁来完成的,Reactor中须要应用程序本身读取或者写入数据,而Proactor模式中,应用程序不须要进行实际的读写过程,它只须要从缓存区读取或者写入便可,操做系统会读取缓存区或者写入缓存区到真正的IO设备.  
   
          综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步 须要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。  



若是你想吃一份宫保鸡丁盖饭: 

同步阻塞:你到饭馆点餐,而后在那等着,还要一边喊:好了没啊! 

同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一下子,就回饭馆喊一声:好了没啊! 

异步阻塞:遛狗的时候,接到饭馆电话,说饭作好了,让您亲自去拿。 

异步非阻塞:饭馆打电话说,咱们知道您的位置,一会给你送过来,安心遛狗就能够了。
 

“一个IO操做其实分红了两个步骤:发起IO请求和实际的IO操做。 
同步IO和异步IO的区别就在于第二个步骤是否阻塞,若是实际的IO读写阻塞请求进程,那么就是同步IO。 
阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,若是阻塞直到完成那么就是传统的阻塞IO,若是不阻塞,那么就是非阻塞IO。 

同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操做并等待或者轮询的去查看IO操做是否就绪,而异步是指用户进程触发IO操做之后便开始作本身的事情,而当IO操做已经完成的时候会获得IO完成的通知。而阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操做的就绪状态来采起的不一样方式,说白了是一种读取或者写入操做函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会当即返回一个状态值。 
因此,IO操做能够分为3类:同步阻塞(即早期的IO操做)、同步非阻塞(NIO)、异步(AIO)。 
同步阻塞: 
在此种方式下,用户进程在发起一个IO操做之后,必须等待IO操做的完成,只有当真正完成了IO操做之后,用户进程才能运行。JAVA传统的IO模型属于此种方式。 

同步非阻塞: 
在此种方式下,用户进程发起一个IO操做之后边可返回作其它事情,可是用户进程须要时不时的询问IO操做是否就绪,这就要求用户进程不停的去询问,从而引入没必要要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。 
异步: 
此种方式下是指应用发起一个IO操做之后,不等待内核IO操做的完成,等内核完成IO操做之后会通知应用程序。” 

这段话比较清楚
 


参考:http://blog.csdn.net/brainkick/article/details/9312407


原文:http://my.oschina.net/bluesky0leon/blog/132361linux

关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就个人理解,给出一个解释:编程

BIO | NIO | AIO,自己的描述都是在Java语言的基础上的。而描述IO,咱们须要从两个层面:windows

  1. 编程语言
  2. 实现原理
  3. 底层基础

从编程语言层面

BIO | NIO | AIO 以Java的角度,理解,linux c里也有AIO的概念(库),这些概念不知道什么缘由被炒火起来,这里只从Java角度入手。缓存

  • BIO,同步阻塞式IO,简单理解:一个链接一个线程
  • NIO,同步非阻塞IO,简单理解:一个请求一个线程
  • AIO,异步非阻塞IO,简单理解:一个有效请求一个线程

BIO

在JDK1.4以前,用Java编写网络请求,都是创建一个ServerSocket,而后,客户端创建Socket时就会询问是否有线程能够处理,若是没有,要么等待,要么被拒绝。即:一个链接,要求Server对应一个处理线程。服务器

NIO

在Java里的由来,在JDK1.4及之后版本中提供了一套API来专门操做非阻塞I/O,咱们能够在java.nio包及其子包中找到相关的类和接口。因为这套API是JDK新提供的I/O API,所以,也叫New I/O,这就是包名nio的由来。这套API由三个主要的部分组成:缓冲区(Buffers)、通道(Channels)和非阻塞I/O的核心类组成。在理解NIO的时候,须要区分,说的是New I/O仍是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。这里讲的是后面一种。网络

NIO自己是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,若是要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通信,就必须使用多线程来处理。也就是说,将每个客户端请求分配给一个线程来单独处理。这样作虽然能够达到咱们的要求,但同时又会带来另一个问题。因为每建立一个线程,就要为这个线程分配必定的内存空间(也叫工做存储器),并且操做系统自己也对线程的总数有必定的限制。若是客户端的请求过多,服务端程序可能会由于不堪重负而拒绝客户端的请求,甚至服务器可能会所以而瘫痪。多线程

NIO基于Reactor,当socket有流可读或可写入socket时,操做系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操做系统。 
也就是说,这个时候,已经不是一个链接就要对应一个处理线程了,而是有效的请求,对应一个线程,当链接没有数据时,是没有工做线程来处理的。架构

AIO

与NIO不一样,当进行读写操做时,只须直接调用API的read或write方法便可。这两种方法均为异步的,对于读操做而言,当有流可读取时,操做系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操做而言,当操做系统将write方法传递的流写入完毕时,操做系统主动通知应用程序。 
便可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 
在JDK1.7中,这部份内容被称做NIO.2,主要在java.nio.channels包下增长了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操做后,直接调用回调函数。


实现原理

说道实现原理,还要从操做系统的IO模型上了解

按照《Unix网络编程》的划分,IO模型能够分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。如何区分呢?首先一个IO操做其实分红了两个步骤:发起IO请求和实际的IO操做,同步IO和异步IO的区别就在于第二个步骤是否阻塞,若是实际的IO读写阻塞请求进程,那么就是同步IO,所以阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,若是不阻塞,而是操做系统帮你作完IO操做再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,若是阻塞直到完成那么就是传统的阻塞IO,若是不阻塞,那么就是非阻塞IO。

收到操做系统的IO模型,又不得不提select/poll/epoll/iocp,关于这四个的理解,很少作解释,本身还没理解到位。

能够理解的说明是:在Linux 2.6之后,java NIO的实现,是经过epoll来实现的,这点能够经过jdk的源代码发现。而AIO,在windows上是经过IOCP实现的,在linux上仍是经过epoll来实现的。

这里强调一点:AIO,这是I/O处理模式,而epoll等都是实现AIO的一种编程模型;换句话说,AIO是一种接口标准,各家操做系统能够实现也能够不实现。在不一样操做系统上在高并发状况下最好都采用操做系统推荐的方式。Linux上尚未真正实现网络方式的AIO。

底层基础

说到底层,要说Linux系统编程,这里本身也不熟悉,有待后来人补充了。 
只笼统的说一个:AIO实现

在windows上,AIO的实现是经过IOCP来完成的,看JDK的源代码,能够发现

WindowsAsynchronousSocketChannelImpl

看实现接口:

implements Iocp.OverlappedChannel

再看实现方法:里面的read0/write0方法是native方法,调用的jvm底层实现,虚拟机技术不熟悉,不献丑了。

在linux上,AIO的实现是经过epoll来完成的,看JDK源码,能够发现,实现源码是:

UnixAsynchronousSocketChannelImpl

看实现接口:

implements Port.PollableChannel

这是与windows最大的区别,poll的实现,在linux2.6后,默认使用epoll。

这样就能够理解了。


写在最后:Java开发为基础的,对于操做系统底层的认知是没有C语言为基础的大牛好的,语言决定了思惟方式,古人诚不欺我


最后,几篇解释的不错的文章:

BIO NIO AIO

NIO.2 入门,第 1 部分: 异步通道 API

使用异步 I/O 大大提升应用程序的性能