概念

  • NIO ( New I/O 也称为 Non-blocking I/O) 是从 Java 1.4 版本开始引入的一种同步 + 非阻塞的通信模式 IO API,可以替代标准的Java IO API,NIO和原来的IO有相同的作用和目的,但使用方式完全不一样,NIO将以更高效的方式进行文件的读写操作

  • NIO 相对于 BIO 来说一大进步,和BIO的区别就是 阻塞变成了非阻塞

解析

为什么会出现?

  • Java IO 的各种流是阻塞的 IO 操作。这就意味着,当一个线程执行读或写 IO 操作时,该线程会被阻塞,直到有一些数据被读取,或者数据完全写入

  • 那为什么会阻塞呢?

    • 因为在一般的 Java IO 操作中,我们以流式的方式,顺序的从一个 Stream 中读取一个或者多个字节,直至读取所有字节。因为它没有缓存区,所以我们就不能随意改变读取指针的位置。

怎么实现非阻塞的?

  • 由上面我们知道BIO 是面向字节流或者字符流的,而在 NIO 中,它摒弃了传统的 IO 流,而是引入了下面三个概念:
    • 通道Channel(可以比作铁轨,用于连接两地)
      • 客户端和服务器之间通过 Channel 通信,和流 Stream 不同,通道是双向的。NIO可以通过 Channel 进行数据的读、写和同时读写操作。
      • Channel 可以非阻塞的读写 IO 操作,而 Stream 只能阻塞的读写 IO 操作。
      • Channel 必须配合 Buffer 使用,总是先读取到一个 Buffer 中,又或者是向一个 Buffer 写入。也就是说,我们无法绕过 Buffer ,直接向 Channel 写入数据。
      • Channel 有非常多的实现类,最为重要的四个 Channel 实现类如下:
        • SocketChannel :一个客户端用来发起 TCP 的 Channel 。
        • ServerSocketChannel :一个服务端用来监听新进来的连接的 TCP 的 Channel 。对于每一个新进来的连接,都会创建一个对应的 SocketChannel 。
        • DatagramChannel :通过 UDP 读写数据。
        • FileChannel :从文件中,读写数据。
    • 缓冲区Buffer(可以比作火车,用于装货)
      • Buffer ,本质上是内存中的一块,我们可以将数据写入这块内存,之后从这块内存获取数据。通过将这块内存封装成 NIO Buffer 对象,并提供了一组常用的方法,方便我们对该块内存的读写。
      • BIO 是将数据直接写入或读取到流 Stream 对象中。
      • NIO 的数据操作都是在 Buffer 中进行的。Buffer 实际上是一个数组。Buffer 最常见的类型是ByteBuffer,另外还有 CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer
    • 多路复用器Selector
      • 它是 Java NIO 得以实现非阻塞 IO 操作的最最最关键。
      • Channel 都会被注册在 Selector 多路复用器上。Selector 通过一个线程不停的轮询这些 Channel ,找出已经准备就绪的 Channel 执行 IO 操作,这样就可以接入成千上万个客户端,这就是 JDK NIO 库的巨大进步。

阻塞与非阻塞 IO

  • Java IO 的各种流是阻塞的 IO 操作。这就意味着,当一个线程执行读或写 IO 操作时,该线程会被阻塞,直到有一些数据被读取,或者数据完全写入。

  • Java NIO 可以让我们非阻塞的使用 IO 操作。例如:

    • 当一个线程执行从 Channel 执行读取 IO 操作时,当此时有数据,则读取数据并返回;当此时无数据,则直接返回而不会阻塞当前线程。
    • 当一个线程执行向 Channel 执行写入 IO 操作时,不需要阻塞等待它完全写入,这个线程同时可以做别的事情。
    • 也就是说,线程可以将非阻塞 IO 的空闲时间用于在其他 Channel 上执行 IO 操作。所以,一个单独的线程,可以管理多个 Channel 的读取和写入 IO 操作。

补充

BIO、NIO 有什么区别

BIO NIO
面向流( Stream ) 面向缓冲区( Buffer )
阻塞的 非阻塞的
Socket 是单向的(不能同时读写) Channel 是双向的 (可以同时读写)
一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。所以,线程开销大。可改良为用线程池的方式代替新创建线程,被称为伪异步 IO 。 一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有新的 I/O 请求时,才启动一个线程进行处理。可改良为一个线程处理多个请求,基于 多 Reactor 模型。

总结

  • 它的特点是要不断主动地去询问数据有没有处理完,一般只适用于连接数目较大但连接时间短的应用,如聊天应用等。

  • 对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发

  • Java提供的NIO的api使用比较复杂,一般建议使用像netty这样的框架,而不要使用jdk自带的api。

参考