Java Nio 二十一

2018/01/28 Java

Java NIO 二十一

使用选择器

选择过程

选择器维护着注册过的通道的集合,并且这些注册关系中的任意一个都是封装在 SelectionKey 对象中的。每一个 Selector 对象维护三个键的集合:


public abstract class Selector
{
        // 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( );
}        

1.已注册的键的集合(Registered key set)

与选择器关联的已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过 keys( )方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发 java.lang.UnsupportedOperationException。

2.已选择的键的集合(Selected key set)

已注册的键的集合的子集。这个集合的每个成员都是相关的通道被选择器(在前一个选择操作 中)判断为已经准备好的,并且包含于键的interest集合中的操作。这个集合通过selectedKeys( )方 法返回(并有可能是空的)。 已选择的键的集合与 ready 集合不同。这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。每个键都有一个内嵌的 ready 集合,指示了所关联的通道已经准备好的 操作。键可以直接从这个集合中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出 java.lang.UnsupportedOperationException。

3.已取消的键的集合(Cancelled key set) 已注册的键的集合的子集,这个集合包含了 cancel( )方法被调用过的键(这个键已经被无效 化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。

在一个刚初始化的 Selector 对象中,这三个集合都是空的。Selector 类的核心是选择过程。选择器是对select( )poll( )等本地调用(native call)或者类似的操作系统特定的系 统调用的一个包装。但是 Selector 所作的不仅仅是简单地向本地代码传送参数。它对每个选择操作应用了特定的过程。对这个过程的理解是合理地管理键和它们所表示的状态信息的基础。

选择操作是当三种形式的 select( )中的任意一种被调用时,由选择器执行的。不管是哪一种形 式的调用,下面步骤将被执行:

  1. 已取消的键的集合将会被检查。
  2. 已注册的键的集合中的键的 interest 集合将被检查。

Selector 类的 select( )方法有以下三种不同的形式,select()在没有通道就绪时将无限阻塞。一旦至少有一个已注册的通道就绪,选择器的选择键 就会被更新,并且每个就绪的通道的 ready 集合也将被更新。返回值将会是已经确定就绪的通道的 数目。正常情况下,这些方法将返回一个非零的值,因为直到一个通道就绪前它都会阻塞。但是它 也可以返回非 0 值,如果选择器的 wakeup( )方法被其他线程调用。

select(long timeout)限制线程等待通道就绪的时间。如果超时时间(以毫秒计算)内没有通道就 绪时,它将返回 0。如果一个或者多个通道在时间限制终止前就绪,键的状态将会被更新,并且方 法会在那时立即返回。将超时参数指定为 0 表示将无限期等待,那么它就在各个方面都等同于使用 无参数版本的 select( )了。

selectNow()是完全非阻塞的,selectNow()方法执行就绪检查过程,但不阻塞。如果当前没有通道就绪,它将立即返回 0。

停止选择过程

Selector的API中的最后一个方法,wakeup( ),提供了使线程从被阻塞的select( )方法中优雅地退出的能力:

public abstract class Selector
{
        // This is a partial API listing
        public abstract void wakeup( );
}

有三种方式可以唤醒在 select( )方法中睡眠的线程:

1.调用 wakeup( )

调用Selector对象的wakeup( )方法将使得选择器上的第一个还没有返回的选择操作立即返 回。如果当前没有在进行中的选择,那么下一次对 select( )方法的一种形式的调用将立即返回。后续的选择操作将正常进行。在选择操作之间多次调用 wakeup( )方法与调用它一次没有什么不同。有时这种延迟的唤醒行为并不是想要的。只想唤醒一个睡眠中的线程,而使得后续的 选择继续正常地进行。可以通过在调用 wakeup( )方法后调用 selectNow( )方法来绕过这个问题。

2.调用 close( )

如果选择器的 close( )方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像 wakeup( )方法被调用了一样。与选择器相关的通道将被注销,而键将被取消。

3.调用 interrupt( )

如果睡眠中的线程的 interrupt( )方法被调用,它的返回状态将被设置。如果被唤醒的线程之后 将试图在通道上执行 I/O 操作,通道将立即关闭,然后线程将捕捉到一个异常。

请注意这些方法中的任意一个都不会关闭任何一个相关的通道。中断一个选择器与中断一个通道是不一样的。选择器不会改变任意一个相关的通道,它只会检查它们的状态。当 一个在 select( )方法中睡眠的线程中断时,对于通道的状态而言,是不会产生歧义的。

Show Disqus Comments

Search

    Table of Contents