NIO

NIO

标签(空格分隔): 异步非阻塞 输入输出流


1. 传统的BIO

BIO(Blocking I/O) 即同步并阻塞I/O, 在NIO(Non-Blocking)出现之前主要使用BIO以及新建线程的方式来解决并发请求, 但是这样很容易收到线程数量瓶颈的限制. 下面是典型的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()....//写数据
          }
      }
    }
}

之所以使用多线程是因为accept, read, write三个函数都是同步阻塞的, 当一个链接存在的时候, 系统是阻塞的, 所以利用多线程让CPU处理更多的申请, 多线程的本质:

  1. 利用CPU的多核特性.
  2. 当I/O阻塞系统, 但CPU空闲的时候, 可以利用多线程使用CPU资源.
  3. 现在的多线程一般都是使用线程池, 可以让线程的创建和回收成本相对较低, 在活动连接数不是特别高的情况下(单机小于1000), 这种模型是比较不错的, 可以让每一个链接专注于自己的I/O并且编程模型相对简单, 也不用考虑系统的过载,限流等问题. 但是当处理百万级别的链接的时候, 使用这种模型肯定是不切实际的, 让CPU去创建这么多的线程是不可能的.

2. 优秀的NIO

从JDK1.4开始, Java提供了一系列用于改进的输入/输出处理的新特性, 被统称为NIO. 新增了许多用于处理输入输出的类, 这些类都被放在java.nio包及其子包之下, 并且对原java.io包中的很多类进行改写, 满足了NIO的功能. NIO采用内存映射文件的方式来处理输入输出, NIO将文件或者文件的一段区域映射到内存中, 这样就可以像访问内存一样访问文件了, NIO与原来的IO有着同样的作用和目的, 但是使用的方法是完全不同的, NIO支持面向缓冲区(Buffer)的基于通道(Channel)的IO操作, NIO将会以更加高效的方式进行文件的读写操作.

2.1 缓冲区Buffer

缓冲区有直接缓冲区和非直接缓冲区之分, 它实际上也是一段内存空间,在NIO库中, 所有的数据都是用缓冲区处理的, 在读取数据的时候, 它是直接读到缓冲区中的, 在写入数据的时候, 他也是直接写入到缓冲区中的, 流程如下图:

2.2 通道Channel

Channel 表示到实体如 硬件设备, 文件,网络套接字或可以执行一个或多个不同I/O操作的程序组件开放的链接.
Channel和传统IO的Stream很相似, 主要的区别为: 通道是双向的, 通过一个Channel即可以读, 也可以写. 而Stream只能进行单向操作. 通道是一个对象, 通过它可以读取和写入数据, 当然所有的数据都通过Buffer对象来处理. 我们不会将字节直接写入通道之中, 相反是将数据写入包含一个或者多个字节的缓冲区. 同样不会直接从通道中读取字节, 而是将数据从通道读入缓冲区, 再从缓冲区获取这个字节.

2.3 选择器Selector

Selector类是NIO的核心类, Selector选择器提供了选择已经就绪的任务的能力. Selector会不断轮询注册在上面所有的Channel, 如果某个Channel为读写等时间做好了准备, 那么久处于就绪状态, 通过Selector 可以不断的轮询发现出就绪的Channel, 进行后续的IO操作.

一个Selector可以同时轮询多个Channel. 这样, 一个单独的线程就可以管理多个Channel,从而管理多个网络连接. 这样就不用为每一个链接都创建一个线程, 同时也避免了多线程之间上下文切换带来的额外开销.

与Selector有关的一个管件类是SelectionKey, 一个SelectionKey表示一个到达的事件, 这两个类构成了服务端处理业务的关键逻辑.

原文地址:https://www.cnblogs.com/A-FM/p/12673857.html