Selector 的出现,大大改善了多个 Java Socket的效率。在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要使用系统调用。而Selector的出现,把就绪选择交给了操作系统(我们熟知的selec函数),把就绪判断和读取数据分开,不仅性能上大有改善,而且使得代码上更加清晰。
Java NIO的选择器部分,实际上有三个重要的类。
1,Selector 选择器,完成主要的选择功能。select(), 并保存有注册到他上面的通道集合。
2,SelectableChannel 可被注册到Selector上的通道。
3,SelectionKey 描述一个Selector和SelectableChannel的关系。并保存有通道所关心的操作。
接下来,便是一个通用的流程。
首先, 创建选择器,
然后,注册通道,
其次,选择就绪通道,
最后,处理已就绪通道数据。
让我们通过代码来看这些步骤是如何完成的。
Selector selector = Selector.open();
channel1.configureBlocking(false);
channel2.configureBlocking(false);
cahnnel3.configureBlocking(false);
SelectionKey key1 = channel1.register(selector, SelectionKey.OP_READ);
SelectionKey key2 = channel2.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
SelectionKey key3 = channel3.register(selector, SelectionKey.OP_WRITE);
while(true){
int readyCount = selector.select(1000);
if( readyCount == 0) continue;
Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
if( key.isReadable()){
readData(key);
}
iter.remove();
}
}
上面的代码是一个示例。我们可以看到,创建一个Selector使用open方法,这是一个静态工厂模式,注意他的异常处理是IOException。接下来的通道,我们并没有说明是什么通道,一般来说,基本上Socket类通道是可选择的,但是文件类的是不可选择的。
我们可以看到的是,这个通道调用了 configureBlocking(false)这样的方法,在注册到Selector上之前,通道应该保证是非阻塞的,否则异常IllegalBlockingModeException抛出。
之后我们开始注册通道,使用registor方法,主意后面一个参数,如果对一个只读的通道注册写操作,是会抛出异常IllegalArgumentException的。例如SocketChannel不支持accept操作。这里一共有四种操作 read,write,accept,connect。
当然,我们还不能把已经关闭的通道注册到Selector中,而Selector如果调用close,那么试图访问它的大多数操作都会抛出异常。
接下来,我们开始使用select函数更新selectedKey,这里比较复杂,但是从代码看,我们做完select以后,就开始便利selectedKey,找到符合要求的key,进行读数据操作。这里还要注意的是,使用完key以后,需要从selectedKey集合中删除。
下面我们还有更详细的说明,因为我们还不知道这个select到底做了说明,selectedKey又是如何更新的呢?
首先,一个selectionKey 包含了两个集合,一个是 注册的感兴趣的操作集合,一个是已经准备好的集合。第一个集合基本上是注册就确定的,或者通过interestOps(int)来改变。select是不会改变interest集合的。但是select改变的是 ready集合。也就是准备好的感兴趣的操作的集合,这样说,也说明,ready集合实际上是interest集合的子集。
如何使用这些集合呢?
看代码:
if (( key.readyOps() & SelectionKey.OP_READ) != 0)
{
myBuffer.clear();
key.channel().read(myBuffer);
doSomething(myBuffer.flip());
}
从上面的代码看出,这个集合只是一个掩码,需要和操作与,才能得到结果。
当然,也有更方便的用法。
if ( key.isReadable() )
还要注意的是,这样的判断并不是就是一定的,只是一个提示。底层通道随时在改变。
对于SelectionKey, 还可以执行cancel操作,一个被cancel掉的SelectionKey,实际上只是被放到了Selector的cancel键集合里,键马上失效,但是通道依然是注册状态,要等到下一个select时才真正取消注册。
现在,我们再来看看选择器做了什么。选择器是就绪选择的核心,它包含了注册到它上面的通道与操作关系的Key,它维护了三个集合。
1,已经注册的键集合 调用, keys()
2,已经选择的键集合 调用, selectedKeys()
3,已经取消的键集合 私有。
选择器虽然封装了select,poll等底层的系统调用,但是她有自己的一套来管理这些键。
每当select被调用时,她做如下检查:
1,检查已经取消的键的集合。如果非空,从其他两个集合中移除已经取消的键,注销相关通道,清空已经取消的键的集合。
2,已注册的键的集合中的键的interest集合被检查。例如有新的interest的操作注册。但是这一步不会影响后面的操作。这是延时到下一次select调用时才会影响的。
就绪条件确认后,底层系统进行查询。依赖于select方法的参数,如果没有通道准备好,根select带的参数超时设置,可能会阻塞线程。
系统调用完成后,可以对操作系统指示的已经准备好的interest集合中的一种操作的通道,执行以下操作:
a: 如果通道的键还没有在已经选择的键的集合中,那么键的ready集合将被清空。然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。
b: 否则,一旦通道的键被放入已经选择的键的集合中时,ready集合不会被清除,而是累积。这就是说,如果之前的状态是ready的操作,本次已经不是ready了,但是他的bit位依然表示是ready,不会被清除。
3, 步骤2可能会有很长一段时间的休眠。所以在步骤2完成以后,步骤1继续执行以确保被取消的键正确处理。
4,返回值,select的返回值说明的是从上一次调用到本次调用,就绪选择的个数。如果上一次就已经是就绪的,那么本次不统计。这是是为何返回为0时,我们continue的原因。
这里使用的延迟注销方法,正是为了解决注销键的问题。如果线程在取消键的同时进行通道注销,那么很可能阻塞并与正在进行的选择操作发生冲突。
同样我们有3中select可以选择:
1, select()
2, select(long timeout)
3, selectNow();
select()会阻塞线程知道又一个通道就绪。
而select带timeout的会在特定时间内阻塞,或者至少有一个通道就绪。
而selectNow()如果没有发现就绪,就直接返回。
如何停止中断选择呢?
有三种方法。
1, wakeup()这是一种优雅的方法,同时也是延时的。如果当前没有正进行的选择操作,也就是要等到下一个select才起作用。
2, close()选择器的close被调用,则所有在选择操作中阻塞的线程被唤醒,相关通道被注销,键也被取消。
3, interrupt() 实际上interrupt并不会中断线程。而是设置线程中断标志。
然后依然是调用wakeup()。这是因为 Selector 捕获了interruptedException,然后在异常处理中调用了 wakeup()
根据以上的信息,我们可以了解到,实际上选择器对选择键中的集合的操作,是交给程序员来完成的。如何管理选择键,是很关键的。
这里需要记住的是,ready集合中的比特位,是累积的。根据步骤2,如果一个键是在选择集合中,那么这个键的ready集合是不会被清除的。而如果这个键不在选择集合中,那么就要首先清空这个键的ready集合,然后把就绪信息更新到这个ready集合上,最后,就是把这个键加入到已选择的集合中。
这也是为什么上面的流程中,我们为什么要把处理的键删除,因为如果不删除,下一次的信息是累积的,我们就不能分出本次select中那些操作就绪了。如果清除掉,那么下一次如果就绪,ready集合就是重置后更新的信息。
分享到:
相关推荐
Java-NIO非阻塞服务器示例 本资源主要讲解了Java-NIO非阻塞服务器的示例,通过使用Java-NIO包来实现非阻塞的服务器端模式。下面是从代码中提取的知识点: 一、Java-NIO包简介 Java-NIO(New I/O)包是Java 1.4...
Java NIO(New IO)是Java 1.4版本引入的一个新模块,它提供了一种不同于传统IO(基于字节流和字符流)的I/O操作方式。传统的IO模型是阻塞式的,而NIO的核心特点是非阻塞,这使得在处理大量并发I/O请求时更为高效。...
- Java NIO的Selector通过轮询检查多个通道的就绪状态,避免了传统I/O中必须为每个连接创建单独线程的开销。 3. **异步连接池** - **连接池**:在高并发环境下,为了减少建立和销毁网络连接的开销,通常会使用...
NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector)。通道可以读写数据,缓冲区用于临时存储数据,选择器则用于监听多个通道的事件,实现了多路复用,从而实现非阻塞I/O。 3. **HttpCore NIO...
"Java NIO Selector 机制解析" Java NIO(New I/O)类库是Java 1.4版本以后引入的新一代I/O机制,相比传统的I/O机制,NIO提供了高效、异步、多路复用的I/O操作模式。Selector机制是NIO类库中的一种核心机制,用于...
选择器(Selector)是NIO中的多路复用机制。通过一个选择器,我们可以监控多个通道的状态,例如是否有数据可读、可写或者有异常发生。这样,一个单独的线程就可以处理多个连接,大大提高了服务器的并发能力。 在...
01-Java NIO-课程简介.mp4 05-Java NIO-Channel-FileChannel详解(一).mp4 06-Java NIO-Channel-FileChannel详解(二).mp4 08-Java NIO-Channel-...23-Java NIO-Selector-示例代码(客户端).mp4 24
**JAVA-NIO程序设计完整实例** Java NIO(New IO)是Java 1.4引入的一个新特性,它为Java提供了非阻塞I/O操作的能力,使得Java在处理I/O时更加高效。NIO与传统的BIO(Blocking I/O)模型相比,其核心在于它允许程序...
传统的Java IO基于流和缓冲区,而NIO则引入了通道(Channel)和选择器(Selector)的概念,使得多路复用变得更加便捷。 在标题中提到的“java-nio.rar_java nio_nio 对象实例化”,我们可以理解为这个压缩包中包含...
### Java-NIO2教程知识点详解 #### I/O发展简史 - **JDK1.0-1.3**: 在此期间,Java的I/O模型主要依赖于传统的阻塞I/O方式,这种模式下,应用程序在等待I/O操作完成时无法执行其他任务,导致效率低下。此外,当时的...
Java NIO(非阻塞I/O)中的Selector是一个核心组件,它允许单个线程处理多个通道(channels)上的I/O事件。Selector的角色就像一个交通指挥员,能够监控多个通道并决定哪个通道准备好进行读写操作,从而提高系统的...
Selector 是 Java NIO 中用于处理多个 Channel 的核心组件。它可以监听多个 Channel 上的 I/O 事件,并且当某个 Channel 准备好进行 I/O 操作时,会通知应用。 ##### 2. Selector 使用流程 使用 Selector 的基本...
它利用Java NIO的Selector机制,实现非阻塞I/O,能够同时处理多个连接请求,提高了系统并发能力。 3. **Entity Encoders/Decoders**:实体编码器和解码器用于处理HTTP消息体,支持各种内容编码格式,如GZIP压缩、...
在Java中,NIO主要由java.nio包及其子包组成,包含通道(Channels)、缓冲区(Buffers)和选择器(Selectors)等核心组件。 【标签】"源码"和"工具"表明这个压缩包可能包含了项目的源代码以及一些辅助开发或分析的...
Java NIO(New Input/Output)是Java标准库在J2SE 1.4及后续版本中引入的一个重要更新,它的出现是为了改进传统的IO模型,提高I/O操作的效率和并发性。NIO的主要特点包括非阻塞I/O、字符转换、缓冲区以及通道等。 1...
Selector是Java NIO框架中的核心组件,它使得单个线程能够管理多个通道(Channels),从而提高系统资源利用率并优化性能。下面我们将详细探讨Java NIO中的Selector机制。 1. **Selector的作用** Selector的主要功能...