NIO学习笔记(3)--选择器

选择器:

选择器基础:

一个或多个可选择的通道注册到选择器对象中。一个表示通道和选择器的键将会被返回。选择键会记住您关心的通道。
当您调用一个选择器对象的select( )方法时,相关的键会被更新,用来检查所有被注册到该选择器的通道。
有两种方式可以选择:
1.被激发的线程可以处于休眠状态,直到一个或者多个注册到选择器的通道就绪。
2.它也可以周期性地轮询选择器,看看从上次检查之后,是否有通道处于就绪状态。
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。

可选择通道(SelectableChannel):

这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。
FileChannel对象不是可选择的,因为它们没有继承SelectableChannel。
所有socket通道都是可选择的,包括从管道(Pipe)对象的中获得的通道
选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系。
选择键对象被SelectableChannel.register( ) 返回并提供一个表示这种注册关系的标记。
选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

SelectableChannel的相关API方法:

        public abstract class SelectableChannel extends AbstractChannel implements Channel { 
            // This is a partial API listing 
            //调用可选择通道的register( )方法会将它注册到一个选择器上(必须是非阻塞模式)。通道一旦被注册,就不能回到阻塞状态。
            public abstract SelectionKey register (Selector sel, int ops) throws ClosedChannelException; 
            public abstract SelectionKey register (Selector sel, int ops, Object att) throws ClosedChannelException; 

            //
            public abstract boolean isRegistered( );
            public abstract SelectionKey keyFor (Selector sel); 
            public abstract int validOps( ); 
            public abstract void configureBlocking (boolean block) throws IOException; 
            public abstract boolean isBlocking( ); 
            public abstract Object blockingLock( ); 
        }

一个或多个可选择的通道注册到选择器对象中。一个表示通道和选择器的键将会被返回。选择键会记住您关心的通道。
当您调用一个选择器对象的select( )方法时,相关的键会被更新,用来检查所有被注册到该选择器的通道。
有两种方式可以选择:
1.被激发的线程可以处于休眠状态,直到一个或者多个注册到选择器的通道就绪。
2.它也可以周期性地轮询选择器,看看从上次检查之后,是否有通道处于就绪状态。

选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
可选择通道(SelectableChannel):
这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。
FileChannel对象不是可选择的,因为它们没有继承SelectableChannel。
所有socket通道都是可选择的,包括从管道(Pipe)对象的中获得的通道。
选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系。
选择键对象被SelectableChannel.register( ) 返回并提供一个表示这种注册关系的标记。
选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

SelectableChannel的相关API方法:

        public abstract class SelectableChannel extends AbstractChannel implements Channel { 
            // This is a partial API listing 
            //调用可选择通道的register( )方法会将它注册到一个选择器上(必须是非阻塞模式)。通道一旦被注册,就不能回到阻塞状态。
            public abstract SelectionKey register (Selector sel, int ops) throws ClosedChannelException; 
            public abstract SelectionKey register (Selector sel, int ops, Object att) throws ClosedChannelException; 

            //
            public abstract boolean isRegistered( );
            public abstract SelectionKey keyFor (Selector sel); 
            public abstract int validOps( ); 
            public abstract void configureBlocking (boolean block) throws IOException; 
            public abstract boolean isBlocking( ); 
            public abstract Object blockingLock( ); 
        }
    通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false)).

Selector类的API:

        public abstract class Selector { 
            public static Selector open( ) throws IOException;
            public abstract boolean isOpen( ); 
            public abstract void close( ) throws IOException; 
            public abstract SelectionProvider provider( ); 
            public abstract int select( ) throws IOException; 
            public abstract int select (long timeout) throws IOException; 
            public abstract int selectNow( ) throws IOException; 
            public abstract void wakeup( ); 
            public abstract Set keys( ); 
            public abstract Set selectedKeys( ); 
        }

一个给定的通道可以被注册到多于一个的选择器上,而且不需要知道它被注册了哪个Selector对象上。

SelectionKey类的api和属性:

        public abstract class SelectionKey { 
            public static final int OP_READ 
            public static final int OP_WRITE 
            public static final int OP_CONNECT 
            public static final int OP_ACCEPT 
            public abstract SelectableChannel channel( ); 
            public abstract Selector selector( ); 
            public abstract void cancel( ); 
            public abstract boolean isValid( ); 
            public abstract int interestOps( ); 
            public abstract void interestOps (int ops); 
            public abstract int readyOps( ); 
            public final boolean isReadable( ) 
            public final boolean isWritable( ) 
            public final boolean isConnectable( ) 
            public final boolean isAcceptable( ) 
            public final Object attach (Object ob) 
            public final Object attachment( ) 
        }

对于键的interest(感兴趣的操作)集合和ready(已经准备好的操作)集合的解释是和特定的通道相关的。
每个通道的实现,将定义它自己的选择键类。
Selector对象是通过调用静态工厂方法open( )来实例化的。类方法open( )向SPI发出请求,通过默认的SelectorProvider对象获取一个新的实例。
当您不再使用它时,需要调用close( )方法来释放它可能占用的资源并将所有相关的选择键设置为无效。
一旦一个选择器被关闭,试图调用它的大多数方法都将导致ClosedSelectorException。
通过相关的选择键的readyOps( )方法返回的就绪状态指示只是一个提示,不是保证。底层的通道在任何时候都会不断改变。

使用选择器:
选择过程:

每一个Selector对象维护三个键的集合:

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

    与选择器关联的已经注册的键的集合。这个已注册的键的集合不是可以直接修改的这个集合通过keys( )方法返回,并且可能是空的。

  • 已选择的键的集合(Selected key set)
    已注册的键的集合的子集。
    这个集合的每个成员都是相关的通道被选择器(在前一个选择操作中)判断为已经准备好的,并且包含了键的interest集合中的操作。
    这个集合通过selectedKeys( )方法返回(并有可能是空的)。
    这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。
    键可以直接从这个集合中移除,但不能添加。

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

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

  1. 已取消的键的集合将会被检查。如果它是非空的,每个已取消的键的集合中的键将从另外两个集合中移除,并且相关的通道将被注销。
    这个步骤结束后,已取消的键的集合将是空的。
  2. 已注册的键的集合中的键的interest集合将被检查。在这个步骤中的检查执行过后,对interest集合的改动不会影响剩余的检查过程。

对于那些还没准备好的通道将不会执行任何的操作。
对于那些操作系统指示至少已经准备好interest集合中的一种操作的通道,将执行以下两种操作中的一种:

a.如果通道的键还没有处于已选择的键的集合中,那么键的ready集合将被清空,
然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。

b.键在已选择的键的集合中。键的ready集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。

3.步骤2可能会花费很长时间,特别是所激发的线程处于休眠状态时。与该选择器相关的键可能会同时被取消。

当步骤2结束时,步骤1将重新执行,以完成任意一个在选择进行的过程中,键已经被取消的通道的注销。
4.select操作返回的值是ready集合在步骤2中被修改的键的数量,而不是已选择的键的集合中的通道的总数。
返回值不是已准备好的通道的总数,而是从上一个select( )调用之后进入就绪状态的通道的数量。
之前的调用中就绪的,并且在本次调用中仍然就绪的通道不会被计入,而那些在前一次调用中已经就绪但已经不再处于就绪状态的通道也不会被计入。
这些通道可能仍然在已选择的键的集合中,但不会被计入返回值中。返回值可能是0。

使用内部的已取消的键的集合来延迟注销,是一种防止线程在取消键时阻塞,并防止与正在进行的选择操作冲突的优化。
Selector类的select( )方法有以下三种不同的形式:

  • 1.无参数版本的select( ).

这种调用在没有通道就绪时将无限阻塞。一旦至少有一个已注册的通道就绪,
选择器的选择键就会被更新,并且每个就绪的通道的ready集合也将被更新。
返回值将会是已经确定就绪的通道的数目。

  • 2.接受一个超时参数的select( )方法的重载形式

这种调用与之前的例子完全相同,除了如果在您提供的超时时间(以毫秒计算)内没有通道就绪时,它将返回0。

  • 3.完全非阻塞的selectNow()

int n = selector.selectNow( );
selectNow()方法执行就绪检查过程,但不阻塞。如果当前没有通道就绪,它将立即返回0。

停止选择过程:

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

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

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

  • 1.调用wakeup( )

                将使得选择器上的第一个还没有返回的选择操作立即返回。
                如果当前没有在进行中的选择,那么下一次对select( )方法的一种形式的调用将立即返回。
                后续的选择操作将正常进行。
                在选择操作之间多次调用wakeup( )方法与调用它一次没有什么不同。
    
  • 2.调用close( )

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

                如果被唤醒的线程之后将试图在通道上执行I/O操作,通道将立即关闭,然后线程将捕捉到一个异常。
    
管理选择键:

通常的做法是在选择器上调用一次select操作(这将更新已选择的键的集合),然后遍历selectKeys( )方法返回的键的集合。
在按顺序进行检查每个键的过程中,相关的通道也根据键的就绪集合进行处理。
然后键将从已选择的键的集合中被移除(通过在Iterator对象上调用remove( )方法),然后检查下一个键。
完成后,通过再次调用select( )方法重复这个循环。
并发性:
选择器对象是线程安全的,但它们包含的键集合不是。
通过keys( )和selectKeys( )返回的键的集合是Selector对象内部的私有的Set对象集合的直接引用。
Iterator对象是快速失败的(fail-fast):如果底层的Set被改变了,它们将会抛出java.util.ConcurrentModificationException,
因此如果您期望在多个线程间共享选择器和/或键,请对此做好准备。
您可以直接修改选择键,但请注意您这么做时可能会彻底破坏另一个线程的Iterator。
异步关闭能力:
任何时候都有可能关闭一个通道或者取消一个选择键。
一个特定的键的集合中的一个键的存在并不保证键仍然是有效的,或者它相关的通道仍然是打开的。
试图使用一个已经失效的键,大多数方法将抛出CancelledKeyException。但是,您可以安全地从从已取消的键中获取通道的句柄。
如果通道已经关闭时,仍然试图使用它的话,在大多数情况下将引发ClosedChannelException。
选择过程的可扩展性:
如果您想要将更多的线程来为通道提供服务,请抵抗住使用多个选择器的欲望。
在大量通道上执行就绪选择并不会有很大的开销,大多数工作是由底层操作系统完成的。
一个更好的策略是对所有的可选择通道使用一个选择器,并将对就绪通道的服务委托给其他线程。
只用一个线程监控通道的就绪状态并使用一个协调好的工作线程池来处理共接收到的数据。
某些通道要求比其他通道更高的响应速度,可以通过使用两个选择器来解决:一个为命令连接服务,另一个为普通连接服务。
通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false)).

    Selector类的API:
        public abstract class Selector { 
            public static Selector open( ) throws IOException;
            public abstract boolean isOpen( ); 
            public abstract void close( ) throws IOException; 
            public abstract SelectionProvider provider( ); 
            public abstract int select( ) throws IOException; 
            public abstract int select (long timeout) throws IOException; 
            public abstract int selectNow( ) throws IOException; 
            public abstract void wakeup( ); 
            public abstract Set keys( ); 
            public abstract Set selectedKeys( ); 
        }
    一个给定的通道可以被注册到多于一个的选择器上,而且不需要知道它被注册了哪个Selector对象上。

    SelectionKey类的api和属性:
        public abstract class SelectionKey { 
            public static final int OP_READ 
            public static final int OP_WRITE 
            public static final int OP_CONNECT 
            public static final int OP_ACCEPT 
            public abstract SelectableChannel channel( ); 
            public abstract Selector selector( ); 
            public abstract void cancel( ); 
            public abstract boolean isValid( ); 
            public abstract int interestOps( ); 
            public abstract void interestOps (int ops); 
            public abstract int readyOps( ); 
            public final boolean isReadable( ) 
            public final boolean isWritable( ) 
            public final boolean isConnectable( ) 
            public final boolean isAcceptable( ) 
            public final Object attach (Object ob) 
            public final Object attachment( ) 
        }
    对于键的interest(感兴趣的操作)集合和ready(已经准备好的操作)集合的解释是和特定的通道相关的。
    每个通道的实现,将定义它自己的选择键类。
    Selector对象是通过调用静态工厂方法open( )来实例化的。类方法open( )向SPI发出请求,通过默认的SelectorProvider对象获取一个新的实例。
    当您不再使用它时,需要调用close( )方法来释放它可能占用的资源并将所有相关的选择键设置为无效。
    一旦一个选择器被关闭,试图调用它的大多数方法都将导致ClosedSelectorException。
    通过相关的选择键的readyOps( )方法返回的就绪状态指示只是一个提示,不是保证。底层的通道在任何时候都会不断改变。
使用选择器:
    选择过程:
        每一个Selector对象维护三个键的集合:
            已注册的键的集合(Registered key set)
                与选择器关联的已经注册的键的集合。这个已注册的键的集合不是可以直接修改的
                这个集合通过keys( )方法返回,并且可能是空的。
            已选择的键的集合(Selected key set)
                已注册的键的集合的子集。
                这个集合的每个成员都是相关的通道被选择器(在前一个选择操作中)判断为已经准备好的,并且包含了键的interest集合中的操作。
                这个集合通过selectedKeys( )方法返回(并有可能是空的)。
                这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。
                键可以直接从这个集合中移除,但不能添加。
            已取消的键的集合(Cancelled key set)
                已注册的键的集合的子集,这个集合包含了cancel( )方法被调用过的键(这个键已经被无效化),但它们还没有被注销。
                这个集合是选择器对象的私有成员,因而无法直接访问。

        在一个刚初始化的Selector对象中,这三个集合都是空的。Selector类的核心是选择过程。
        选择器是对select( )、poll( )等本地调用(native call)或者类似的操作系统特定的系统调用的一个包装。
        但是Selector所作的不仅仅是简单地向本地代码传送参数,它对每个选择操作应用了特定的过程。
        对这个过程的理解是合理地管理键和它们所表示的状态信息的基础。
        选择操作是当三种形式的select( )中的任意一种被调用时,由选择器执行的。不管是哪一种形式的调用,下面步骤将被执行:
        1.已取消的键的集合将会被检查。如果它是非空的,每个已取消的键的集合中的键将从另外两个集合中移除,并且相关的通道将被注销。
            这个步骤结束后,已取消的键的集合将是空的。
        2.已注册的键的集合中的键的interest集合将被检查。在这个步骤中的检查执行过后,对interest集合的改动不会影响剩余的检查过程。
            对于那些还没准备好的通道将不会执行任何的操作。
            对于那些操作系统指示至少已经准备好interest集合中的一种操作的通道,将执行以下两种操作中的一种:
                a.如果通道的键还没有处于已选择的键的集合中,那么键的ready集合将被清空,
                    然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。
                b.键在已选择的键的集合中。键的ready集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。
        3.步骤2可能会花费很长时间,特别是所激发的线程处于休眠状态时。与该选择器相关的键可能会同时被取消。
            当步骤2结束时,步骤1将重新执行,以完成任意一个在选择进行的过程中,键已经被取消的通道的注销。
        4.select操作返回的值是ready集合在步骤2中被修改的键的数量,而不是已选择的键的集合中的通道的总数。
            返回值不是已准备好的通道的总数,而是从上一个select( )调用之后进入就绪状态的通道的数量。
            之前的调用中就绪的,并且在本次调用中仍然就绪的通道不会被计入,而那些在前一次调用中已经就绪但已经不再处于就绪状态的通道也不会被计入。
            这些通道可能仍然在已选择的键的集合中,但不会被计入返回值中。返回值可能是0。

        使用内部的已取消的键的集合来延迟注销,是一种防止线程在取消键时阻塞,并防止与正在进行的选择操作冲突的优化。

        Selector类的select( )方法有以下三种不同的形式:
            1.无参数版本的select( ).
                这种调用在没有通道就绪时将无限阻塞。一旦至少有一个已注册的通道就绪,
                选择器的选择键就会被更新,并且每个就绪的通道的ready集合也将被更新。
                返回值将会是已经确定就绪的通道的数目。
            2.接受一个超时参数的select( )方法的重载形式
                这种调用与之前的例子完全相同,除了如果在您提供的超时时间(以毫秒计算)内没有通道就绪时,它将返回0。
            3.完全非阻塞的selectNow()
                int n = selector.selectNow( ); 
                selectNow()方法执行就绪检查过程,但不阻塞。如果当前没有通道就绪,它将立即返回0。
    停止选择过程:
        wakeup( ),提供了使线程从被阻塞的select( )方法中优雅地退出的能力: 
            public abstract class Selector {
                // This is a partial API listing 
                public abstract void wakeup( ); 
            }
            有三种方式可以唤醒在select()方法中睡眠的线程:
            1.调用wakeup( )
                将使得选择器上的第一个还没有返回的选择操作立即返回。
                如果当前没有在进行中的选择,那么下一次对select( )方法的一种形式的调用将立即返回。
                后续的选择操作将正常进行。
                在选择操作之间多次调用wakeup( )方法与调用它一次没有什么不同。
            2.调用close( )
                如果选择器的close( )方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像wakeup( )方法被调用了一样。
                与选择器相关的通道将被注销,而键将被取消。
            3.调用interrupt( )
                如果被唤醒的线程之后将试图在通道上执行I/O操作,通道将立即关闭,然后线程将捕捉到一个异常。
    管理选择键:
        通常的做法是在选择器上调用一次select操作(这将更新已选择的键的集合),然后遍历selectKeys( )方法返回的键的集合。
        在按顺序进行检查每个键的过程中,相关的通道也根据键的就绪集合进行处理。
        然后键将从已选择的键的集合中被移除(通过在Iterator对象上调用remove( )方法),然后检查下一个键。
        完成后,通过再次调用select( )方法重复这个循环。
    并发性:
        选择器对象是线程安全的,但它们包含的键集合不是。
        通过keys( )和selectKeys( )返回的键的集合是Selector对象内部的私有的Set对象集合的直接引用。
        Iterator对象是快速失败的(fail-fast):如果底层的Set被改变了,它们将会抛出java.util.ConcurrentModificationException,
        因此如果您期望在多个线程间共享选择器和/或键,请对此做好准备。
        您可以直接修改选择键,但请注意您这么做时可能会彻底破坏另一个线程的Iterator。
    异步关闭能力:
        任何时候都有可能关闭一个通道或者取消一个选择键。
        一个特定的键的集合中的一个键的存在并不保证键仍然是有效的,或者它相关的通道仍然是打开的。
        试图使用一个已经失效的键,大多数方法将抛出CancelledKeyException。但是,您可以安全地从从已取消的键中获取通道的句柄。
        如果通道已经关闭时,仍然试图使用它的话,在大多数情况下将引发ClosedChannelException。
    选择过程的可扩展性:
        如果您想要将更多的线程来为通道提供服务,请抵抗住使用多个选择器的欲望。
        在大量通道上执行就绪选择并不会有很大的开销,大多数工作是由底层操作系统完成的。
        一个更好的策略是对所有的可选择通道使用一个选择器,并将对就绪通道的服务委托给其他线程。
        只用一个线程监控通道的就绪状态并使用一个协调好的工作线程池来处理共接收到的数据。
        某些通道要求比其他通道更高的响应速度,可以通过使用两个选择器来解决:一个为命令连接服务,另一个为普通连接服务。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值