Skip to content

Java NIO概述

1. Java IO的历史发展

IO 的操作方式通常分为几种:同步阻塞 BIO、同步非阻塞 NIO、异步非阻塞 AIO。

  1. 在 JDK1.4 之前,我们建立网络连接的时候采用的是 BIO 模式。
  2. Java NIO(New IO 或 Non Blocking IO)是从 Java 1.4 版本开始引入的一个新的IO API,可以替代标准的 Java IO API。NIO 支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。BIO与NIO一个比较重要的不同是,我们使用BIO的时候往往会引入多线程,每个连接对应一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,让连接共用一个线程。
  3. AIO 也就是 NIO 2,在 Java 7中引入了NIO 的改进版NIO 2,它是异步非阻塞的IO模型。

1.1 阻塞 IO (BIO)

阻塞 IO(BIO)是最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象,直至有可供读取的数据或者数据能够写入。

  1. 在BIO模式中,服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求,这种模式虽然简单方便,但由于服务器为每个客户端的连接都采 用一个线程去处理,使得资源占用非常大。因此,当连接数量达到上限时,如果再有用户请求连接,直接会导致资源瓶颈,严重的可能会直接导致服务器崩溃。
  2. 大多数情况下为了避免上述问题,都采用了线程池模型。也就是创建一个固定大小的线程池,如果有客户端请求,就从线程池中取一个空闲线程来处理,当客户端处 理完操作之后,就会释放对线程的占用。因此这样就避免为每一个客户端都要创建线程带来的资源浪费,使得线程可以重用。但线程池也有它的弊端,如果连接大多是长 连接,可能会导致在一段时间内,线程池中的线程都被占用,那么当再有客户端请求连接时,由于没有空闲线程来处理,就会导致客户端连接失败。 传统的BIO模式如下图所示:
    Alt text

1.2 非阻塞 IO(NIO)

基于 BIO 的各种弊端,在 JDK1.4开始出现了高性能 IO 设计模式非阻塞 IO(NIO)。

  1. NIO 采用非阻塞模式,基于Reactor模式的工作方式,I/O 调用不会被阻塞,它的实现过程是:会先对每个客户端注册感兴趣的事件,然后有一个线程专门去轮询每个客户端是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询。如下图所示:
    Alt text
  2. NIO中实现非阻塞I/O的核心对象就是SelectorSelector就是注册各种I/O事件地方,而且当我们感兴趣的事件发生时,就是这个对象告诉我们所发生的事件,如下图所示:
    Alt text NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,一个选择器线程可以同时处理成千上万个连接,系统不必创 建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。
IONIO
面向流面向缓冲区
阻塞IO非阻塞IO
选择器Selectors

1.3 异步非阻塞IO(AIO)

  1. AIO也就是NIO 2,在 Java7中引入了NIO的改进版NIO 2,它是异步非阻塞的IO模型。异步IO是基于事件和回调机制实现的,也就是说AIO模式不需要selector操作,而是是事件驱动形式,也就是当客户端发送数据之后,会主动通知服务器,接着服务器再进行读写操作。
  2. Java的AIO API其实就是Proactor模式的应用,和Reactor模式类似。Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,
    Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备。

2. NIO的组成

Java NIO 由以下几个核心部分组成:

  • Channels
  • Buffers
  • Selectors
    虽然Java NIO中除此之外还有很多类和组件,但Channel,Buffer和Selector构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。

2.1. Channel

Channel,可以翻译成"通道"。Channel和 IO中的 Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。NIO 中的 Channel 的主要实现有:FileChannel、DatagramChannel、 SocketChannel和ServerSocketChannel,这里看名字就可以猜出个所以然来:分别可以对应文件 IO、UDP和TCP(Server和Client)

2.2 Buffer

NIO 中的关键 Buffer 实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。

2.3 Selector

Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用 Selector, 得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等。

2.4 Channel Buffer Selector三者关系

  1. 一个Channel就像一个流,只是Channel是双向的,Channel读数据到Buffer,Buffer写数据到Channel。
    Alt text
  2. 一个Selector允许一个线程处理多个Channel。
    Alt text