【java总结】关于BIO、NIO、AIO的理解

来源:互联网 发布:linux java gcj java 编辑:程序博客网 时间:2024/06/06 06:58

在JavaIO中,提供了3种IO,分别是BIO,NIO和AIO。在学习这三个之前,我们需要先了解一些概念。

 

IO操作

我们知道,一个程序需要经常读取一些外设的信息,如硬盘,显卡上的信息,这些操作被称为IO操作,以读操作为例,IO操作可以被分为两个阶段:

①查看数据是否准备就绪,

②进行数据拷贝。

对于计算机来说,IO操作是非常耗时的,应为CPU跟外设之间的速度极度不匹配,相对CPU的速度来说,IO操作是非常慢的。

 

同步/异步

同步IO下,需要不断轮询数据是否准备就绪,当数据准备就绪时,再将数据拷贝到用户线程。在IO操作发生时,直到IO操作完成之前,整个进程都会被阻塞。也就是说,多个IO同时发生时,其中任何一个IO操作都会阻塞其他IO操作。

异步IO下,一个线程请求IO操作,并不会导致线程被阻塞,在用户发出请求后,便不再管它,IO操作由内核自动完成,然后由内核发送通知告诉用户线程IO操作已完成,多个IO同时发生时,都同时进行,互不阻塞。

区别

它们之间的区别便是在IO操作第二个阶段中,数据拷贝是否需要进程参与。

在需要进程参与数据拷贝的同步IO下,无疑需要消耗进程的时间片(即CPU分配给进程的一段时间),在这段时间内进程只是等待IO操作的完成,无法做其他事情。

在不需要进程参与数据拷贝的异步IO下,进程得到了释放,可以自己做其他事情不用傻傻地在等待IO操作完成。

 

阻塞/非阻塞

当一个程序发出一个IO操作请求时,如果该请求得不到满足(即对应的设备繁忙),针对情况的两种表现便是阻塞和非阻塞的区别。在阻塞情况下,进程会在这里一直等待(即进程阻塞),直到对应设备能够重新工作。在非阻塞情况下,会立刻返回一个标志信息告知不能满足该请求。

区别

它们之间的区别便是在IO操作的第一个阶段中,如果数据没有就绪时是选择继续等待还是选择立刻返回一个标志,让CPU能继续执行其他工作。

 

理解了以上概念之后,就可以开始BIO/NIO/AIO的学习。

 

BIO

Block IO,阻塞IO,是一种同步阻塞型的IO,在JDK1.4以前这是JAVA中的唯一选择。


原理:

在服务器端调用accept()方法等待客户端的连接请求,如果没有连接则一直阻塞在这里。一旦有客户端进行连接,便建立通信套接字进行读写操作,一般情况下,此时不能接受其他客户端的连接,只能够等待当前IO完成后才可以继续下一个连接。为了可以处理多用户,则必须用到多线程,一个请求过来,服务器便分配给它一个线程对客户请求进行处理。这种一一对应的处理方式,当客户变多的时候,线程的开销是巨大的,系统性能将急剧下降。
而且,当一个线程处理IO需要的时间很长的时候,其他的线程还未被服务的线程可能就要等待很久了。


适用场景:

BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

 

NIO

No-block IO,非阻塞IO,在JDK1.4之后提供了API。特征是同步非阻塞,一个IO请求一个线程。


原理:

NIO采用了事件驱动的思想,用单个线程来提供了BIO中用多线程方式提供的服务。

它使用了一种高性能IO设计模式:Reactor模式。利用一个叫做多路复用器Selector的线程来监听所有客户端的IO事件,如客户端的连接请求,客户端通过套接字发送数据这个write事件,或者客户端要求从服务器端得到某些数据的read事件,这些都由同一个线程进行管理,它负责在这些事件到达的时候触发。这个线程不停地轮询,直到有IO请求时才启动一个新线程进行处理。这样的话,服务器不用每次有新客户端连接就分配给它一个线程,而只需要在多路复用器上进行注册就可以进行管理。并且只有在真正的IO事件发生时,才会分配线程去执行IO事件,这种设计模式大大减少了资源占用。

NIO中所有的数据都利用缓冲区Buffer来处理,访问和写入NIO,都是通过Buffer这个数据结构。而我们对Buffer的访问要通过通道Channel,它不同于单向的流的地方便是它是全双工的,可以同时用于写操作和读操作。上面提到的多路复用器Selector会不断轮询注册在上面的Channel,如果Channel发生IO操作,这个Channel就处于就绪状态,可能被Selector读取出来,获取Channel的集合进行后续的IO操作。

下面通过一段代码更好地理解NIO

 

while (true) {     // select()阻塞,等待有事件发生唤醒      int selected = selector.select();      if (selected > 0) {          Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();          while (selectedKeys.hasNext()) {              SelectionKey key = selectedKeys.next();              if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {                  // 处理 accept 事件              } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {                  // 处理 read 事件              } else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {                  // 处理 write 事件              }              selectedKeys.remove();          }      }  }  



我们知道NIO是同步非阻塞的int selected = selector.select();  这句代码中,它却是阻塞到有事件发生才唤醒,即事件驱动。这个是否与NIO同步非阻塞的特点前后矛盾了呢?在这里我们复习一下同步和阻塞的概念,其实这里的select的阻塞,针对的是IO操作的第一个阶段,也就是说,其实这是同步的一种表现。在同步IO下,需要不断轮询数据是否准备就绪,当数据准备就绪时,再将数据拷贝到用户线程。我们可以看到,针对accept,write,read等事件的操作便是非阻塞的,它针对的是IO操作的第二阶段,数据拷贝阶段。

 

NIO适用场景

适合处理连接数目特别多,但是连接比较短(轻操作)的场景

 

AIO:

异步IO,从JDK7开始支持。特征是异步非阻塞,一个有效IO一个线程。需要操作系统支持异步IO


原理:

使用了高性能IO设计模式:Proactor模式。跟Reactor模式类似,但它是异步非阻塞的,也就是说,当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,发送一个通知告知操作已完成,在这里事件处理器关注读取完成事件而不是读取就绪事件


使用说明:

read/write方法都是异步的,完成后会主动调用回调函数。当进行读写操作时,只须直接调用APIreadwrite方法即可。在这种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,真正的IO读取或者写入操作已经由内核完成了。


适用场景:

AIO方式使用于连接数目多且连接比较长(重操作)的架构,充分调用OS参与并发操作。

0 0