NIO - 施用选择键

NIO - 使用选择键
    ◇ SelectionKey API
        看看 SelectionKey 的相关 API 方法:
package java.nio.channels;
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();
}

        一个键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。前两个方法中反映了这种关系:channel() 方法返回与该键相关的 SelectableChannel 对象,而 selector() 则返回相关的 Selector 对象。

        键对象表示了一种特定的注册关系。当应该终结这种关系的时候,可以调用 SelectionKey 对象的 cancel() 方法。可以通过调用 isValid() 方法来检查它是否仍然表示一种有效的关系。当键被取消时,它将被放在相关的选择器的已取消的键的集合里。注册不会立即被取消,但键会立即失效。

        当通道关闭时,所有相关的键会自动取消(记住,一个通道可以被注册到多个选择器上)。当选择器关闭时,所有被注册到该选择器的通道都将被注销,并且相关的键将立即被无效化(取消)。一旦键被无效化,调用它的与选择相关的方法就将抛出 CancelledKeyException。

    ◇ instrest 集合和 ready 集合
        一个 SelectionKey 对象包含两个以整数形式进行编码的比特掩码:一个用于指示那些通道/选择器组合体所关心的操作(instrest 集合),另一个表示通道准备好要执行的操作(ready 集合)。
        当前的 interest 集合可以通过调用键对象的 interestOps() 方法来获取。最初,这应该是通道被注册时传进来的值。这个 interset 集合永远不会被选择器改变,但你可以通过调用 interestOps() 方法并传入一个新的比特掩码参数来改变它。
        interest 集合也可以通过将通道注册到选择器上来改变(实际上使用一种迂回的方式调用 interestOps())。当相关的 Selector 上的 select() 操作正在进行时改变键的 interest 集合,不会影响那个正在进行的选择操作。所有更改将会在 select() 的下一个调用中体现出来。
        可以通过调用键的 readyOps() 方法来获取相关的通道的已经就绪的操作。ready 集合是 interest 集合的子集,并且表示了 interest 集合中从上次调用 select() 以来已经就绪的那些操作。

        就像之前提到过的那样,有四个通道操作可以被用于测试就绪状态。你可以像上面的代码那样,通过测试比特掩码来检查这些状态,但 SelectionKey 类定义了四个便于使用的布尔方法来为您测试这些比特值:isReadable(),isWritable(),isConnectable() 和 isAcceptable()。每一个方法都与使用特定掩码来测试 readyOps( )方法的结果的效果相同:
if (key.isWritable())

等价于:

if ((key.readyOps() & SelectionKey.OP_WRITE) != 0)

        这四个方法在任意一个 SelectionKey 对象上都能安全地调用。不能在一个通道上注册一个它不支持的操作,这种操作也永远不会出现在 ready 集合中。调用一个不支持的操作将总是返回 false,因为这种操作在该通道上永远不会准备好。

        通过相关的选择键的 readyOps() 方法返回的就绪状态指示只是一个提示,不是保证。底层的通道在任何时候都会不断改变。其他线程可能在通道上执行操作并影响它的就绪状态。同时,操作系统的特点也总是需要考虑的。

    ◇ SelectionKey 附件
        继续来看 SelectionKey 的 attach 和 attachment API:
public abstract class SelectionKey {
    // This is a partial API listing
    public final Object attach(Object ob)
    public final Object attachment();
}

        这两个方法允许您在键上放置一个“附件”,并在后面获取它。这是一种允许你将任意对象与键关联的便捷的方法。这个对象可以引用任何对您而言有意义的对象,例如业务对象、会话句柄、其他通道等等。这将允许你遍历与选择器相关的键,使用附加在上面的对象句柄作为引用来获取相关的上下文。

        attach() 方法将在键对象中保存所提供的对象的引用。SelectionKey 类除了保存它之外,不会将它用于任何其他用途。任何一个之前保存在键中的附件引用都会被替换。可以使用 null 值来清除附件。可以通过调用 attachment() 方法来获取与键关联的附件句柄。

        如果选择键的存续时间很长,但您附加的对象不应该存在那么长时间,请记得在完成后清理附件。否则,您附加的对象将不能被垃圾回收,您将会面临内存泄漏问题。

        回忆下 SelectableChannel 的 register() 方法,有个接受一个 Object 参数的重载方法如下:
SelectionKey key = channel.register (selector, SelectionKey.OP_READ, myObject);

等价于:

SelectionKey key = channel.register (selector, SelectionKey.OP_READ);
key.attach (myObject);


    ◇ SelectionKey 的并发性
        关于 SelectionKey 的最后一件需要注意的事情是并发性。总体上说,SelectionKey 对象是线程安全的,但知道修改 interest 集合的操作是通过 Selector 对象进行同步的是很重要的。这可能会导致 interestOps() 方法的调用会阻塞不确定长的一段时间。选择器所使用的锁策略(例如是否在整个选择过程中保持这些锁)是依赖于具体实现的。幸好,这种多元处理能力被特别地设计为可以使用单线程来管理多个通道。被多个线程使用的选择器也只会在系统特别复杂时产生问题。