在详细了解 Selector API 之前,你需要知道一点和 Selector 内部工作原理相关的知识。就像在 NIO - 使用选择键 中]探讨的那样,选择器维护着注册过的通道的集合,并且这些注册关系中的任意一个都是封装在 SelectionKey 对象中的。每一个 Selector 对象维护三个键的集合:
public abstract class Selectory {
// This is a partial API listing
public abstract Set keys();
public abstract Set selectedKeys();
public abstract int select() throws IOException;
public abstract int select(long timeout) throws IOException;
public abstract int selectNow() throws IOException;
public abstract void wakeup();
}
- 已注册的键的集合(Registered key set)
与选择器关联的已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过 keys() 方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引 java.lang.UnsupportedOperationException。
- 已选择的键的集合(Selected key set)
已注册的键的集合的子集。这个集合的每个成员都是相关的通道被选择器(在前一个选择操作中)判断为已经准备好的,并且包含于键的 interest 集合中的操作。这个集合通过 selectedKeys() 方法返回(并有可能是空的)。
不要将已选择的键的集合与 ready 集合弄混了。这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。每个键都有一个内嵌的 ready 集合,指示了所关联的通道已经准备好的操作。
键可以直接从这个集合(已选择的键的集合)中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出 java.lang.UnsupportedOperationException。
- 已取消的键的集合(Cancelled key set)
已注册的键的集合的子集,这个集合包含了 cancel() 方法(SelectionKey 类的方法)被调用过的键(这个键已经被无效化),但它们还没有被注销 。这个集合是选择器对象的私有成员,因而无法直接访问。
在一个刚初始化的 Selector 对象中,以上提到的三个集合都是空的。
◇ 选择过程
Selector 类的核心是选择过程。基本上来说,选择器是对 select()、poll() 等本地调用(native call)或者类似的操作系统特定的系统调用的一个包装。但是 Selector 所作的不仅仅是简单地向本地代码传送参数。它对每个选择操作应用了特定的过程。对这个过程的理解是合理地管理键和它们所表示的状态信息的基础。
Selector 类的 select() 方法有以下三种不同的形式:
- int select()
这种调用在没有通道就绪时将无限阻塞。一旦至少有一个已注册的通道就绪,选择器的选择键就会被更新,并且每个就绪的通道的 ready 集合也将被更新。返回值将会是已经确定就绪的通道的数目。正常情况下,这些方法将返回一个非零的值,因为直到一个通道就绪前它都会阻塞。
- int select(long timeout)
这种调用与之前的例子完全相同,除了如果在你提供的超时时间(以毫秒计算)内没有通道就绪时,它将返回 0。如果一个或者多个通道在时间限制终止前就绪,键的状态将会被更新,并且方法会在那时立即返回。将超时参数指定为 0 表示将无限期等待,那么它就在各个方面都等同于使用无参数版本的 select() 了。
- int selectNow()
这种种形式是完全非阻塞的。selectNow() 方法执行就绪检查过程,但不阻塞。如果当前没有通道就绪,它将立即返回 0。
选择操作是当三种形式的 select() 中的任意一种被调用时,由选择器执行的。不管是哪一种形式的调用,下面步骤将被执行:
1. 已取消的键的集合将会被检查。如果它是非空的,每个已取消的键的集合中的键将从另外两个集合中移除,并且相关的通道将被注销。这个步骤结束后,已取消的键的集合将是空的。
2. 已注册的键的集合中的键的 interest 集合将被检查。在这个步骤中的检查执行过后,对 interest 集合的改动不会影响剩余的检查过程。
一旦就绪条件被定下来,底层操作系统将会进行查询,以确定每个通道所关心的操作的真实就绪状态。依赖于特定的 select() 方法调用,如果没有通道已经准备好,线程可能会在这时阻塞,通常会有一个超时值。对于那些还没准备好的通道将不会执行任何的操作。对于那些操作系统指示至少已经准备好 interest 集合中的一种操作的通道,将执行以下两种操作中的一种:
- 如果通道的键还没有处于已选择的键的集合中,那么键的 ready 集合将被清空,然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。
- 否则,也就是键在已选择的键的集合中。键的 ready 集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。所有之前的已经不再是就绪状态的操作不会被清除。事实上,所有的比特位都不会被清理。由操作系统决定的 ready 集合是与之前的 ready 集合按位分离的,一旦键被放置于选择器的已选择的键的集合中,它的 ready 集合将是累积的。比特位只会被设置,不会被清理。假设之前的 ready 集合为 100,此次 010 的操作已就绪,此时的 ready 集合为 110,而不是 010。这就是累积,不会被清理。
3. 步骤 2 可能会花费很长时间,特别是所激发的线程处于休眠状态时。与该选择器相关的键可能会同时被取消。当步骤 2 结束时,步骤 1 将重新执行,以完成任意一个在选择进行的过程中,键已经被取消的通道的注销。
4. select 操作返回的值是 ready 集合在步骤 2 中被修改的键的数量,而不是已选择的键的集合中的通道的总数。返回值不是已准备好的通道的总数,而是从上一个 select() 调用之后进入就绪状态的通道的数量。之前的调用中就绪的,并且在本次调用中仍然就绪的通道不会被计入,而那些在前一次调用中已经就绪但已经不再处于就绪状态的通道也不会被计入。这些通道可能仍然在已选择的键的集合中,但不会被计入返回值中。返回值可能是 0。
◇ 停止选择过程
Selector 的 API 中的最后一个方法,wakeup(),提供了使线程从被阻塞的 select() 方法中优雅地退出的能力:
public abstract class Selector {
// This is a partial API listing
public abstract void wakeup();
}
有三种方式可以唤醒在 select() 方法中睡眠的线程:
- 调用 wakeup()
调用 Selector 对象的 wakeup() 方法将使得选择器上的第一个还没有返回的选择操作立即返回。
- 调用 close()
如果选择器的 close() 方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像 wakeup() 方法被调用了一样。与选择器相关的通道将被注销,而键将被取消。
- 调用 interrupt()
如果睡眠中的线程的 interrupt() 方法被调用,它的返回状态将被设置。如果被唤醒的线程之后将试图在通道上执行 I/O 操作,通道将立即关闭,然后线程将捕捉到一个异常。使用 wakeup() 方法将会优雅地将一个在 select() 方法中睡眠的线程唤醒。如果你想让一个睡眠的线程在直接中断之后继续执行,需要执行一些步骤来清理中断状态(参见 Thread.interrupted() 的相关文档)。
Selector 对象将捕捉 InterruptedException 异常并调用 wakeup() 方法。请注意这些方法中的任意一个都不会关闭任何一个相关的通道。中断一个选择器与中断一个通道是不一样的。选择器不会改变任意一个相关的通道,它只会检查它们的状态。当一个在 select() 方法中睡眠的线程中断时,对于通道的状态而言,是不会产生歧义的。
◇ 管理选择键
选择是累积的。一旦一个选择器将一个键添加到它的已选择的键的集合中,它就不会移除这个键。并且,一旦一个键处于已选择的键的集合中,这个键的 ready 集合将只会被设置,而不会被清理。乍一看,这好像会引起麻烦,因为选择操作可能无法表现出已注册的通道的正确状态。它提供了极大的灵活性,但把合理地管理键以确保它们表示的状态信息不会变得陈旧的任务交给了程序员。
合理地使用选择器的秘诀是理解选择器维护的选择键集合所扮演的角色(参见 选择过程 小节,特别是选择过程的第二步)。最重要的部分是当键已经不再在已选择的键的集合中时将会发生什么。当通道上的至少一个感兴趣的操作就绪时,键的 ready 集合就会被清空,并且当前已经就绪的操作将会被添加到 ready 集合中。该键之后将被添加到已选择的键的集合中。
清理一个 SelectKey 的 ready 集合的方式是将这个键从已选择的键的集合中移除。选择键的就绪状态只有在选择器对象在选择操作过程中才会修改。处理思想是只有在已选择的键的集合中的键才被认为是包含了合法的就绪信息的。这些信息将在键中长久地存在,直到键从已选择的键的集合中移除,以通知选择器你已经看到并对它进行了处理。如果下一次通道的一些感兴趣的操作发生时,键将被重新设置以反映当时通道的状态并再次被添加到已选择的键的集合中。
这种框架提供了很多灵活性。通常的做法是在选择器上调用一次 select 操作(这将更新已选择的键的集合),然后遍历 selectKeys() 方法返回的键的集合。在按顺序进行检查每个键的过程中,相关的通道也根据键的就绪集合进行处理。然后键将从已选择的键的集合中被移除(通过在 Iterator 对象上调用 remove() 方法),然后检查下一个键。完成后,通过再次调用 select() 方法重复这个循环。
分享到:
相关推荐
它使用Java NIO的选择器,监控连接的读写事件。 - **RequestExecutor**: 处理HTTP请求和响应,与HTTP协议栈交互。 - **ConnectionEndpoint**: 表示一个网络连接,管理连接的生命周期,包括建立、保持和关闭连接。 ...
- **选择器使用**:通过Selector监听多个通道的就绪状态,实现多路复用,提高程序并发性能。 - **非阻塞I/O**:展示如何使用非阻塞模式避免线程被长时间阻塞,提高系统资源利用率。 这个Demo将帮助你理解如何在实际...
相比之下,NIO(Non-blocking I/O)通过选择器和通道的概念,使得一个线程可以同时处理多个连接,极大地提高了服务端的并发处理能力。Matrix-NIO就是利用这种机制,为开发者提供了一套易于使用的API,用于构建可扩展...
NIO的核心概念包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。下面我们将深入探讨这些知识点。 首先,我们来看BIO(Blocking I/O)模型。在BIO中,每一个连接都需要一个线程来处理,当服务端接收...
6. **SelectionKey**:表示通道在选择器上的注册状态,可以设置和查询感兴趣的事件。 7. **Selector.select()**:阻塞等待至少一个通道准备就绪,然后返回一组键表示哪些通道已准备就绪。 8. **read()** 和 **write...
1. **高性能爬虫**:HTTPCore-NIO的高效连接管理和非阻塞特性使其成为构建高性能网络爬虫的理想选择,可以轻松处理大量并发请求。 2. **微服务通信**:在微服务架构中,HTTPCore-NIO可以作为服务间通信的基础,提供...
与传统的I/O模型(BIO)不同,NIO不依赖于线程池来处理并发连接,而是通过选择器(Selector)来轮询和监听通道(Channel)上的事件。这使得NIO在资源利用率和性能上有了显著提升。 在"socket-nio-single-reactor....
此外,NIO还提供了选择器(Selectors),可以监控多个通道的状态,提高了服务器的并发处理能力。 **3. AIO(异步I/O)** AIO,也称为NIO.2,进一步优化了I/O模型。在AIO中,读写操作是非阻塞的,并且操作系统会在...
总结,Java NIO的选择器是实现高并发I/O的关键组件,通过合理使用选择器,开发者可以构建出高效、可扩展的网络应用程序。在实际开发中,理解并熟练掌握选择器的使用,将极大地提升系统的性能和稳定性。
2. **选择器(Selectors)**:选择器允许单线程处理多个通道,提高了并发处理能力。通过注册感兴趣的事件(如读、写、连接、接受),可以在一个线程中监控多个通道的状态。 3. **缓冲区(Buffers)**:包括`...
NIO基于通道(Channel)和缓冲区(Buffer)进行数据传输,且提供了选择器(Selector)来监听多个通道的事件,实现了一对多的模型。这样,服务器端可以同时处理多个连接,提高了系统资源利用率。 压缩包中的`demo3.txt`...
该项目使用NIO通道和选择器允许异步联网,以便可以与React性应用程序一起使用。 该项目中的所有代码旨在使文档清晰易懂,因此您无需成为密码学专家即可了解其用法。 出色的单元测试覆盖率将成为常态 使用外部加密...
- **主要内容**:介绍Selector(选择器)的概念及其在多路复用中的作用。 - **学习目标**:理解Selector的基本原理。 #### 22. Java NIO-Selector-操作方法 - **主要内容**:详细讲解Selector的操作方法,如注册、...
相比之下,NIO通过选择器(Selector)和通道(Channel)提供了非阻塞的数据读写,允许单个线程处理多个连接,显著提高了性能。NIO-EPOOL模型是NIO模型的一种优化,通常结合ExecutorPool(执行器池)来管理线程,...
通过使用Selector,我们可以设计出一种选择器模式,将多个通道注册到同一个选择器上,然后在单个线程中轮询选择器,处理那些准备好的通道,从而实现高并发的网络服务。 总的来说,本教程将围绕ServerSocketChannel...
- **处理事件**:从Selector返回的迭代器中,我们可以遍历每个准备好的事件,然后进行相应的处理,如接收数据、发送数据或关闭通道。 - **重复循环**:这个过程可以不断重复,直到应用程序退出或不再需要处理I/O...
此外,NIO还提供了选择器(Selector),允许单个线程管理多个通道,提高了并发处理能力,尤其适用于大量连接的服务器端应用。 在IO模型方面,面试中可能会考察阻塞IO、非阻塞IO和多路复用IO。阻塞IO是最基础的模型...
3. **选择器(Selector)**:选择器允许单个线程检查多个通道上的事件,如读、写或连接完成,从而实现高效的多路复用。 NIO的主要优点包括: 1. **非阻塞**:在读写数据时,如果数据未准备好,线程不会被阻塞,...
选择器 java nio主要的核心组件 缓冲区 buffer 通道 Channels 选择器 Selectors java nio缓冲区buffer 简介 Buffer是数据的容器,在nio中负责数据的存取,java为不同数据类型提供了相对应的缓冲区类型 如:...