三、JAVA NIO概述

本文详细介绍了Java NIO的三种IO模型(BIO、NIO、AIO)及其区别,重点讲解了Channel、Buffer和Selector的角色,并对比了NIO与BIO在性能和资源利用上的优势。NIO的核心在于非阻塞操作,适用于高并发场景,通过Selector实现多线程复用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NIO 目录



三、JAVA NIO概述

1、IO 概述

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

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

下面我们来详细介绍这几种 IO 方式

1.2、同步并阻塞 IO (BIO)

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

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

3-1

1.3、同步非阻塞 IO(NIO)

基于 BIO 的各种弊端,在 JDK1.4 开始出现了高性能 IO 设计模式非阻塞 IO(NIO)。对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。

  • (1)NIO 采用非阻塞模式,基于 Reactor 模式的工作方式,I/O 调用不会被阻塞,它的实现过程是:会先对每个客户端注册感兴趣的事件,然后有一个线程专门去轮询每个客户端是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询。如下图所示:

3-2

  • (2)NIO 中实现非阻塞 I/O 的核心对象就是 Selector,Selector 就是注册各种 I/O事件的地方,而且当我们感兴趣的事件发生时,就是这个对象告诉我们所发生的事件,如下图所示:

3-3

  • (3)NIO 的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,一个选择器线程可以同时处理成千上万个连接,系统不必创建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。

  • (4)非阻塞指的是 IO 事件本身不阻塞,但是获取 IO 事件的 select()方法是需要阻塞等待的。区别是BIO会阻塞在 IO 操作上, NIO 阻塞在事件获取上,没有事件就没有 IO,从高层次看 IO 就不阻塞了。也就是说只有 IO 已经发生那么我们才评估 IO 是否阻塞,但是select()阻塞的时候 IO 还没有发生,何谈 IO 的阻塞呢?

    NIO 的本质是延迟 IO 操作到真正发生 IO 的时候,而不是以前的只要 IO 流打开了就一直等待 IO 操作。

3-4

1.4、异步非阻塞 IO(AIO)

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

2、NIO 概述

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

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

2.1、Channel

首先说一下 Channel,可以翻译成 “通道”。Channel 和 IO 中的 Stream(流)是差不多一个等级的。只不过 Stream 是单向的,譬如:InputStream, OutputStream。而Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作。

NIO 中的 Channel 的主要实现有:FileChannelDatagramChannelSocketChannel 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。

3-5

  • (2)一个 selector 允许一个线程处理多个 channel。

3-6

2.5 NIO与BIO的对比

  • NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。

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

    • BIO模型中,一个连接来了,会创建一个线程,对应一个while死循环,死循环的目的就是不断监测这条连接上是否有数据可以读,大多数情况下,1w个连接里面同一时刻只有少量的连接有数据可读,因此,很多个while死循环都白白浪费掉了,因为读不出啥数据。
    • 而在NIO模型中,他把这么多while死循环变成一个死循环,这个死循环由一个线程控制。NIO中,新来一个连接不再创建一个新的线程,而是可以把这条连接直接绑定到一个固定的线程,然后这条连接的所有读写都由这个线程来负责。那么NIO如何做到一个线程,一个while死循环就能监测1w个连接是否有数据可读的呢?这就是NIO模型中selector的作用,一条连接来了之后,现在不创建一个while死循环去监听是否有数据可读了,而是直接把这条连接注册到selector上,然后,通过检查这个selector,就可以批量监测出有数据可读的连接,进而读取数据
  • NIO和BIO之间第一个最大的区别是:

    • BIO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
    • NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
  • 关于读:

    • IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

    • NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。

  • 关于写:

    • 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
      • Channel中数据的读取是通过Buffer , 一种非阻塞的读取方式。
        • 你既可以读取也可以写入到Channel,流只能读取或者写入,inputStream和outputStream。
        • Channel可以异步的读和写。
        • Channel永远都是从一个buffer中读或者写入到一个buffer中去。
      • Selector 多路复用器 单线程模型,线程的资源开销相对比较小。一个selector同时检查一组信道的I/O状态。Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值