java NIO BIO和AIO

来源:互联网 发布:仿真软件proteus下载 编辑:程序博客网 时间:2024/06/08 14:17

借鉴于:https://zhuanlan.zhihu.com/p/23488863


NIO:NEW IOnot blocking IO,非阻塞的io模型,也是i/o多路复用的基础;

传统的iobio(blocking i/o),阻塞io;使用bio的经典方式如下:

{

 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池

 

 ServerSocket serverSocket = new ServerSocket();

 serverSocket.bind(8088);

 while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来

 Socket socket = serverSocket.accept();

 executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程

}

 

class ConnectIOnHandler extends Thread{

    private Socket socket;

    public ConnectIOnHandler(Socket socket){

       this.socket = socket;

    }

    public void run(){

      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件

          String someThing = socket.read()....//读取数据

          if(someThing!=null){

             ......//处理数据

             socket.write()....//写数据

          }

       }

    }

}

经典的每连接每线程的模型,之所以使用多线程,主要原因在于socket.accept()socket.read()socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。其实这也是所有使用多线程的本质:

1)利用多核。

2)I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。

现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。

 

不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很""的资源,主要表现在:

a线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。

b线程本身占用较大内存,像Java的线程栈,一般至少分配512K1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。

c线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。

d容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。

 

所以出现了not blocking i/o用于解决上面的问题

Nio特点:socket主要的读写注册和接收函数,在等待阶段都是非阻塞的,真正的I/O操作同步阻塞的.

 

BIO\NIO\AIO的比较:

所有的系统I/O分为两个部分:等待就绪和操作.如读函数,分为等待系统可读和真正的读;但等待就绪不使用CPU,但真正的读写操作的是使用cpu,当然操作执行期间也是一种阻塞.

socket.read()为例:

对于BIO:socket,read(),如果TCP RecvBuffer里面没有数据,函数会一直阻塞,直到收到数据,返回读到的数据.

NIO:如果TCP RecvBuffer里面没有数据,则直接返回0,永远不会阻塞;如果有数据,则直接把数据从网卡读到内存,此时进程需要间断查询数据是否已经读完..

AIO:不但等待就绪是非阻塞的,就连数据从网卡读到内存的过程也是异步的,此时不再需要进程去查询是否已经读完,而是直接可以干别的事,数据已经读完时会主动告知进程

 

NIO的使用:

NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。

  interface ChannelHandler{

      void channelReadable(Channel channel);

      void channelWritable(Channel channel);

   }

   class Channel{

     Socket socket;

     Event event;//读,写或者连接

   }

 

   //IO线程主循环:

   class IoThread extends Thread{

   public void run(){

   Channel channel;

   while(channel=Selector.select()){//选择就绪的事件和对应的连接

      if(channel.event==accept){

         registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器

      }

      if(channel.event==write){

         getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件

      }

      if(channel.event==read){

          getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件

      }

    }

   }

   Map<ChannelChannelHandler> handlerMap;//所有channel的对应事件处理器

  }

这个程序很简短,也是最简单的Reactor模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。


借鉴于:https://zhuanlan.zhihu.com/p/23488863

原创粉丝点击