type
status
date
urlname
summary
tags
category
icon
password
catalog
sort
在Java中,IO(输入/输出)操作是核心组成部分,尤其是在网络编程和文件操作中。随着Java的发展,IO模型也在不断进化,以适应不同的应用场景和性能需求。本文将详细介绍Java中的三种主要IO模型:阻塞IO(BIO)、非阻塞IO(NIO)和异步非阻塞IO(AIO)。
notion image
 
💡
同步与异步IO模型是处理输入输出操作时的两种不同方式,它们在数据传输的效率、复杂性和适用场景上各有特点。下面我将从概念、特点和应用场景等方面详细讲解这两种IO模型。
同步IO模型
同步IO模型是指用户空间的线程(或进程)主动发起IO请求,并等待IO操作完成,期间线程会被阻塞,直到IO操作完成并返回结果后,线程才能继续执行后续操作。
特点‌:
  1. 阻塞性‌:IO操作期间,用户线程会被挂起,无法执行其他任务,直到IO操作完成。
  1. 简单性‌:编程模型简单直观,易于理解和实现。
  1. 资源消耗‌:在高并发场景下,由于每个连接都需要一个独立的线程来维护,因此会消耗大量的线程资源和内存资源。
应用场景‌:
同步IO模型适用于并发量不大、对资源消耗不敏感的场景。在Java中,传统的BIO(Blocking IO)模型就是典型的同步IO模型。
异步IO模型
异步IO模型是指用户空间的线程发起IO请求后,立即返回继续执行后续操作,而IO操作由内核或其他线程在后台完成。当IO操作完成后,通过回调机制或其他方式通知用户线程处理结果。
特点‌:
  1. 非阻塞性‌:用户线程在发起IO请求后不会被阻塞,可以继续执行其他任务。
  1. 高效性‌:能够显著提高程序的并发处理能力,减少线程资源的消耗。
  1. 复杂性‌:编程模型相对复杂,需要处理回调机制、事件通知等异步编程特有的问题。
应用场景‌:
异步IO模型适用于高并发、对性能要求较高的场景。在Java中,NIO(New IO)和AIO(Asynchronous IO)模型都支持异步IO操作。特别是AIO模型,它提供了完全的异步IO支持,能够在不阻塞用户线程的情况下进行IO操作。
总结
同步与异步IO模型各有优缺点,选择哪种模型取决于具体的应用场景和需求。在需要高并发、低延迟的场景下,异步IO模型是更好的选择;而在并发量不大、对性能要求不高的场景下,同步IO模型则更为简单实用。在实际开发中,应根据具体需求灵活选择和使用这两种IO模型。

阻塞IO(BIO)

BIO模型在JDK 1.4之前是网络编程的标准。它的特点是每个客户端连接都会生成一个线程来处理,这种模式在客户端数量较少时工作良好,但是随着客户端数量的增加,线程数量激增,导致资源浪费和性能下降。
为了避免这个问题,通常会使用线程池来重用线程资源。线程池可以限制同时存在的线程数量,提高线程的利用率。但是,如果客户端连接大多是长连接,线程可能会长时间被占用,导致线程池中没有空闲线程来处理新的客户端连接。
传统的Java IO(Input/Output)模型是基于流(Stream)的,通过字节流和字符流进行输入和输出操作。这种模型在处理大量并发请求时,由于每个请求需要独占一个线程,可能导致线程资源的浪费和性能瓶颈。在未使用异步模型之前我们的开发流程是这样的从各个服务获取数据最常见的是同步调用,如下图所示:
notion image
在同步调用的场景下,接口耗时长、性能差,接口响应时长T > T1+T2+T3+……+Tn,这时为了缩短接口的响应时间,一般会使用线程池的方式并行获取数据,合同详情的组装正是使用了这种方式。
notion image
这种方式由于以下两个原因,导致资源利用率比较低:
  • CPU资源大量浪费在阻塞等待上,导致CPU资源利用率低。在Java 8之前,一般会通过回调的方式来减少阻塞,但是大量使用回调,又引发臭名昭著的回调地狱问题,导致代码可读性和可维护性大大降低。
  • 为了增加并发度,会引入更多额外的线程池,随着CPU调度线程数的增加,会导致更严重的资源争用,宝贵的CPU资源被损耗在上下文切换上,而且线程本身也会占用系统资源,且不能无限增加。
同步模型下,会导致硬件资源无法充分利用,系统吞吐量容易达到瓶颈。
 

同步非堵塞(NIO)

NIO的异步模型主要体现在其非阻塞I/O操作上。与传统的阻塞I/O模型不同,NIO允许一个线程处理多个连接的I/O操作,而不会导致线程阻塞。这种模型极大地提高了系统的并发处理能力,减少了线程资源的消耗。
Java中的NIO于Java1.4中引入,对应java.nio包,提供了Channel, Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的I/O操作方法。 对于高负载、高并发的(网络)应用,应使用NIO。Java中的NIO可以看作是I/O多路复用模型。也有很多人认为,Java中的NIO属于同步非阻塞IO模型
我们先来看看 同步非阻塞 IO 模型。
notion image
 
同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝 到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。 相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。 但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。 这个时候,I/O 多路复用模型 就上场了。
notion image
  • IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
  • 目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
  • IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
  • Java 中的 NIO ,有一个非常重要的 选择器 ( Selector ) 的概念,也可以被称为 多路复用器。 通过它只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
 

核心组件

NIO的异步模型主要依赖于以下几个核心组件
  • Channel(通道)‌:数据的载体,可以是文件、网络连接等。与流(Stream)不同,Channel是双向的,可以同时用于读和写操作。
  • Buffer(缓冲区)‌:数据的中转站,所有通过Channel读写的数据都必须先放入Buffer中,或从Buffer中取出。Buffer提供了对数据的结构化访问,以及跟踪系统的读/写进程。
  • Selector(选择器)‌:NIO的核心,用于检查一个或多个Channel的状态是否处于非阻塞模式,从而实现单个线程管理多个Channel的目的。
notion image
 

工作原理与步骤

NIO的异步模型工作原理可以概括为以下几个步骤
  1. 注册Channel与Selector‌:将Channel注册到Selector上,并指定感兴趣的事件(如读就绪、写就绪等)。
  1. Selector监听事件‌:Selector会不断地轮询注册在其上的Channel,检查是否有感兴趣的事件发生。
  1. 处理事件‌:当检测到某个Channel上有感兴趣的事件发生时,Selector会通知相应的线程(或线程池中的线程)来处理这些事件。处理过程中,可以从Channel中读取数据到Buffer中,或者将Buffer中的数据写入Channel中。
  1. 继续监听‌:处理完事件后,线程会回到Selector处继续监听其他Channel上的事件。
notion image
 
使用NIO模型进行网络编程通常包括以下几个步骤
  1. 创建ServerSocketChannel和SocketChannel对象,用于监听客户端连接和进行数据读写。
  1. 创建Selector对象,用于监听ServerSocketChannel和SocketChannel的事件。
  1. 将ServerSocketChannel注册到Selector中,并指定监听的事件类型(如连接事件、读写事件等)。
  1. 创建Buffer对象,用于存储读写数据。
  1. 使用Selector的select()方法等待事件发生。当有事件发生时,通过Selector的selectedKeys()方法获取事件列表,并遍历列表处理事件。
  1. 在事件处理函数中,使用Buffer对象进行数据的读写操作。
  1. 最后关闭相关资源,包括Channel、Buffer和Selector对象。
 
notion image
Java NIO 的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO 采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
事件名
对应值
服务端接收客户端连接事件
SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件
SelectionKey.OP_CONNECT(8)
读事件
SelectionKey.OP_READ(1)
写事件
SelectionKey.OP_WRITE(4)
NIO的异步特性主要体现在以下几个方面
  • 非阻塞I/O‌:通过Selector和Channel的配合,实现了非阻塞的I/O操作,即线程在等待I/O操作完成时不会被挂起。
  • 事件驱动‌:NIO基于事件驱动机制,只有当发生感兴趣的事件时,才会触发相应的处理逻辑。
  • 高性能‌:由于非阻塞和事件驱动的特性,NIO能够显著提高系统的并发处理能力和吞吐量。
 
NIO的优点:每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
NIO的缺点:需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。
Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。
 
NIO的异步模型非常适用于需要处理大量并发连接和I/O操作的场景,如大型应用服务器、网络聊天室、实时游戏服务器等。在这些场景中,NIO能够有效地减少线程资源的消耗,提高系统的稳定性和可扩展性。
综上所述,NIO的异步模型是Java中实现高性能网络编程的重要技术之一。通过其核心组件(Channel、Buffer、Selector)和独特的工作原理(非阻塞I/O、事件驱动),NIO为开发者提供了强大的异步处理能力,使得构建高性能、高并发的网络应用成为可能。
 

通道(Channel)

首先说一下Channel,国内大多翻译成“通道”。Channel的角色和OIO中的Stream(流)是差不多的。在OIO中,同一个网络连接会关联到两个流:一个输入流(Input Stream),另一个输出流(Output Stream),Java应用程序通过这两个流,不断地进行输入和输出的操作。
在NIO中,一个网络连接使用一个通道表示,所有的NIO的IO操作都是通过连接通道完成的。一个通道类似于OIO中的两个流的结合体,既可以从通道读取数据,也可以向通道写入数据。
notion image
Channel和Stream的一个显著的不同是:Stream是单向的,譬如InputStream是单向的只读流,OutputStream是单向的只写流;而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
NIO中的Channel的主要实现有:
  • FileChannel 用于文件IO操作
  • DatagramChannel 用于UDP的IO操作
  • SocketChannel 用于TCP的传输操作
  • ServerSocketChannel 用于TCP连接监听操作
Java NIO相对于旧的java.io库来说,并不是要取代,而是提出的三个新的设计思路:
  • 对原始类型的读/写缓冲的封装
  • 基于的读写机制,对Stream的进一步抽象。
  • 事件轮询/反应设计模式(即Selector机制)
按上述思路,而机制是作为的进一步抽象而产生的,那么和相比有什么不同呢?按字面理解实际上就可以获得信息:作为流是有方向的,而则只是通道,并没有指明方向。因此,读写操作都可以在同一个里实现。的命名强调了nio中数据输入输出对象的通用性,为非阻塞的实现提供基础。
在的实现里,也存在只读通道和只写通道,这两种通道实际上抽象了的读写行为。
至于的IO阻塞状态读写,则和传统的java.io包类似。但多了一层缓冲而已。因此,按照原来的设计思路来用nio也是可行的,不过nio的设计本质上还是非阻塞输入输出控制,把控制权重新交给程序员。
因此,java.nio从设计角度看,就不是替代java.io包,而是为java.io提供更多的控制选择。
 
scatter/gather
Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel中读取或者写入到的操作。
ReadableByteChannelWritableByteChannel接口提供了通道的读写功能,而ScatteringByteChannelGatheringByteChannel接口都新增了两个以缓冲区数组作为参数的相应方法。
scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的中,这样你可以方便的处理消息头和消息体。
分散(scatter):在读操作时将读取的数据写入多个buffer中。因此,将从中读取的数据“分散(scatter)”到多个中。如下图描述:
例如:
注意首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照在数组中的顺序将从中读取的数据写入到,当一个被写满后,紧接着向另一个中写。
Scattering Reads在移动下一个前,必须填满当前的,这也意味着它不适用于动态消息(消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。
聚集(gather):在写操作时将多个buffer的数据写入同一个,因此, 将多个中的数据“聚集(gather)”后发送到Channel。如下图描述:
例如:
Buffer``数组是write()方法的入参,write()方法会按照Buffer在数组中的顺序,将数据写入到Channel,注意只有positionlimit之间的数据才会被写入。因此,如果一个Buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到Channel`中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。
 
 

缓冲区(Buffer)

应用程序与通道(Channel)主要的交互,主要是进行数据的read读取和write写入。为了完成NIO的非阻塞读写操作,NIO为大家准备了第三个重要的组件——NIO Buffer(NIO缓冲区)。
 
Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组。Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。
notion image
所谓通道的读取,就是将数据从通道读取到缓冲区中;所谓通道的写入,就是将数据从缓冲区中写入到通道中。缓冲区的使用,是面向流进行读写操作的OIO所没有的,也是NIO非阻塞的重要前提和基础之一。
 
 
Buffer 本质是一个内存块,基于数组实现,Buffer 是一个抽象类,七个基本类型(除了 boolean)都有 Buffer 的子类,最常用的是 ByteBuffer,因为网络数据都是以字节方式传输的。
上面也说到,buffer 既可以读又可以写,但是读写要做切换,也就是说,如果 buffer 当前模式为写模式,则要显式切换到读模式才可以读数据,反之亦然。
这对的操作有两种模式:读模式写模式
读模式的目标区域为数据填充区,游标在数据填充区移动,为已写数据的边界;写模式的目标区域为数据空白区,游标在数据空白区移动,为的容量边界。
当向写入数据时,会记录下写了多少数据。一旦要读取数据,需要通过方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用或方法。方法会清空整个缓冲区方法只会清除已经读过的数据,任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
下面以 ButeBuffer 为例,演示一下 Buffer 的使用。
这个方法命名给人的感觉就是将数据清空了,但是实际上却不是的,它并没有清空缓冲区中的数据,只是重置了对象中的三个索引值。因此,假设此次该Buffer中的数据是满的,下次读取的数据不足以填满缓冲区,那么就会存在上一次遗留下来的的数据,所以在判断缓冲区中是否还有可用数据时,使用hasRemaining()方法,在JDK中,这个方法的代码如下:
在该方法中,比较了和的值,用以判断是否还有可用数据,上次的遗留数据被隔离在之外,所以不会干扰本次的数据处理。
 
Java NIO中的Buffer用于和NIO通道进行交互,是将数据移进移出通道的唯一方式。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Java的NIO使用ByteBufferCharBufferDoubleBufferFloatBufferIntBufferLongBufferShortBuffer覆盖了能通过IO发送的基本数据类型,还有个Mappedyteuffer用于表示内存映射文件。
Buffer类是一个抽象类,对应于Java的主要数据类型,在NIO中有8种缓冲区类,分别如下:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。
前7种Buffer类型,覆盖了能在IO中传输的所有的Java基本数据类型。第8种类型MappedByteBuffer是专门用于内存映射的一种ByteBuffer类型。不同的Buffer子类,其能操作的数据类型能够通过名称进行判断,比如IntBuffer只能操作Integer类型的对象。
实际上,使用最多的还是ByteBuffer二进制字节缓冲区类型
 
 

选择器(Selector

Java NIO的选择器允许一个单独的线程来监视多个输入通道,多个通道可以共用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
 
选择器(Selector)是什么呢?选择器和通道的关系又是什么?
简单地说:选择器的使命是完成IO的多路复用,其主要工作是通道的注册、监听、事件查询。一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出)状况。选择器和通道的关系,是监控和被监控的关系。
选择器提供了独特的API方法,能够选出(select)所监控的通道已经发生了哪些IO事件,包括读写就绪的IO操作事件。
在NIO编程中,一般是一个单线程处理一个选择器,一个选择器可以监控很多通道。所以,通过选择器,一个单线程可以处理数百、数千、数万、甚至更多的通道。在极端情况下(数万个连接),只用一个线程就可以处理所有的通道,这样会大量地减少线程之间上下文切换的开销。
 
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
调用通道的 Channel.register(Selector sel,int ops)方法,可以将通道实例注册到一个选择器中。register方法有两个参数:第一个参数,指定通道注册到的选择器实例;第二个参数,指定选择器要监控的IO事件类型。
 
可供选择器监控的通道IO事件类型,包括以下四种:
  1. 可读事件:SelectionKey.OP_READ
  1. 可写事件:SelectionKey.OP_WRITE
  1. 客户端连接服务端事件:SelectionKey.OP_CONNECT
  1. 服务端接收客户端连接事件:SelectionKey.OP_ACCEPT
以上的事件类型常量定义在SelectionKey类中。如果选择器要监控通道的多种事件,可以用“按位或”运算符来实现。例如,同时监控可读和可写IO事件:
Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannelSelector一起使用,因为FileChannel不能切换到非阻塞模式,而套接字通道都可以。
如果对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:
还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey有四个方法连判断是否为某个事件,与上面的四种事件相对应:
  1. selectionKey.isAcceptable();
  1. selectionKey.isConnectable();
  1. selectionKey.isReadable();
  1. selectionKey.isWritable();
什么是IO事件呢?
这个概念容易混淆,这里特别说明一下。这里的IO事件不是对通道的IO操作,而是通道处于某个IO操作的就绪状态,表示通道具备执行某个IO操作的条件。
比方说某个SocketChannel传输通道,如果完成了和对端的三次握手过程,则会发生“连接就绪”(OP_CONNECT)的事件。
再比方说某个ServerSocketChannel服务器连接监听通道,在监听到一个新连接的到来时,则会发生“接收就绪”(OP_ACCEPT)的事件。
还比方说,一个SocketChannel通道有数据可读,则会发生“读就绪”(OP_READ)事件;一个等待写入数据的SocketChannel通道,会发生写就绪(OP_WRITE)事件。
说明:Socket连接事件的核心原理,和TCP连接的建立过程有关。关于TCP协议的核心原理和连接建立时三次握手和四次挥手知识,请参阅本书后面的有关TCP协议原理的部分内容。
 
SelectableChannel可选择通道
并不是所有的通道,都是可以被选择器监控或选择的。
比方说,FileChannel文件通道就不能被选择器复用。判断一个通道能否被选择器监控或选择,有一个前提:判断它是否继承了抽象类SelectableChannel(可选择通道),如果是则可以被选择,否则不能。
简单地说,一条通道若能被选择,必须继承SelectableChannel类。
SelectableChannel类,是何方神圣呢?
它提供了实现通道的可选择性所需要的公共方法。Java NIO中所有网络链接Socket套接字通道,都继承了SelectableChannel类,都是可选择的。而FileChannel文件通道,并没有继承SelectableChannel,因此不是可选择通道。
 
 
SelectionKey选择键
通道和选择器的监控关系注册成功后,就可以选择就绪事件。具体的选择工作,和调用选择器Selector的select()方法来完成。通过select方法,选择器可以不断地选择通道中所发生操作的就绪状态,返回注册过的感兴趣的那些IO事件。
换句话说,一旦在通道中发生了某些IO事件(就绪状态达成),并且是在选择器中注册过的IO事件,就会被选择器选中,并放入SelectionKey选择键的集合中。
 
这里出现一个新的概念——SelectionKey选择键。SelectionKey选择键是什么呢?
简单地说,SelectionKey选择键就是那些被选择器选中的IO事件。前面讲到,一个IO事件发生(就绪状态达成)后,如果之前在选择器中注册过,就会被选择器选中,并放入SelectionKey选择键集合中;如果之前没有注册过,即使发生了IO事件,也不会被选择器选中。SelectionKey选择键和IO的关系,可以简单地理解为:选择键,就是被选中了的IO事件。
 
在实际编程时,选择键的功能是很强大的。通过SelectionKey选择键,不仅仅可以获得通道的IO事件类型,比方说SelectionKey.OP_READ;还可以获得发生IO事件所在的通道;另外,也可以获得选出选择键的选择器实例。
选择器使用流程
使用选择器,主要有以下三步:
  1. 获取选择器实例;
  1. 将通道注册到选择器中;
  1. 轮询感兴趣的IO就绪事件(选择键集合)。
第一步:获取选择器实例。选择器实例是通过调用静态工厂方法open()来获取的,具体如下:
Selector选择器的类方法open()的内部,是向选择器SPI(SelectorProvider)发出请求,通过默认的SelectorProvider(选择器提供者)对象,获取一个新的选择器实例。Java中SPI全称为(ServiceProvider Interface,服务提供者接口),是JDK的一种可以扩展的服务提供和发现机制。Java通过SPI的方式,提供选择器的默认实现版本。也就是说,其他的服务提供商可以通过SPI的方式,提供定制化版本的选择器的动态替换或者扩展。
 
 
第二步:将通道注册到选择器实例。要实现选择器管理通道,需要将通道注册到相应的选择器上,简单的示例代码如下:
上面通过调用通道的register(…)方法,将ServerSocketChannel通道注册到了一个选择器上。当然,在注册之前,首先需要准备好通道。
这里需要注意:注册到选择器的通道,必须处于非阻塞模式下,否则将抛出 IllegalBlockingModeException异常。
这意味着,FileChannel文件通道不能与选择器一起使用,因为FileChannel文件通道只有阻塞模式,不能切换到非阻塞模式;而Socket套接字相关的所有通道都可以。
其次,还需要注意:一个通道,并不一定要支持所有的四种IO事件。
例如服务器监听通道ServerSocketChannel,仅仅支持Accept(接收到新连接)IO事件;而传输通道SocketChannel则不同,该类型通道不支持Accept类型的IO事件。
如何判断通道支持哪些事件呢?
可以在注册之前,可以通过通道的validOps()方法,来获取该通道所有支持的IO事件集合。
 
第三步:选出感兴趣的IO就绪事件(选择键集合)。
通过Selector选择器的select()方法,选出已经注册的、已经就绪的IO事件,并且保存到SelectionKey选择键集合中。
SelectionKey集合保存在选择器实例内部,其元素为SelectionKey类型实例。调用选择器的selectedKeys()方法,可以取得选择键集合。
接下来,需要迭代集合的每一个选择键,根据具体IO事件类型,执行对应的业务操作。大致的处理流程如下:
处理完成后,需要将选择键从这个SelectionKey集合中移除,防止下一次循环的时候,被重复的处理。
SelectionKey集合不能添加元素,如果试图向SelectionKey选择键集合中添加元素,则将抛出java.lang.UnsupportedOperationException异常。
 
用于选择就绪的IO事件的select()方法,有多个重载的实现版本,具体如下:
  1. select():阻塞调用,一直到至少有一个通道发生了注册的IO事件。
  1. select(long timeout):和select()一样,但最长阻塞时间为timeout指定的毫秒数。
  1. selectNow():非阻塞,不管有没有IO事件,都会立刻返回。
select()方法的返回值的是整数类型(int),表示发生了IO事件的数量。更准确地说,是从上一次select到这一次select之间,有多少通道发生了IO事件,更加准确地说,是指发生了选择器感兴趣(注册过)的IO事件数。
 
 
 
 
 
 
 
 
 
Java IO — NIO BufferElasticsearch — 索引(Mapping Index)
Loading...
Honesty
Honesty
人道洛阳花似锦,偏我来时不逢春
最新发布
Java IO — NIO Buffer
2024-10-21
Java IO — NIO Channel
2024-10-21
Java IO — IO/NIO模型
2024-10-21
Java异步编程方式介绍
2024-10-21
Elasticsearch — 索引(Mapping Index)
2024-10-19
Elasticsearch — 如何存储数据并保持一致性?
2024-10-19