您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2013年08月23日 07:49

原创 Java nio入门教程详解(三十三)

1380 次浏览 读完需要≈ 11 分钟

内容目录

4.2 使用选择键

让我们看看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()方法来检查它是否仍然表示一种有效的关系。当键被取消时,它将被放在相关的选择器的已取消的键的集合里。注册不会立即被取消,但键会立即失效(参见 4.3 节)。当再次调用select()方法时(或者一个正在进行的select()调用结束时),已取消的键的集合中的被取消的键将被清理掉,并且相应的注销也将完成。通道会被注销,而新的SelectionKey将被返回。

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

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

可以通过调用键的readyOps()方法来获取相关的通道的已经就绪的操作。ready集合是interest集合的子集,并且表示了interest 集合中从上次调用 select()以来已经就绪的那些操作。例如,下面的代码测试了与键关联的通道是否就绪。如果就绪,就将数据读取出来,写入一个缓冲区,并将它送到一个consumer(消费者)方法中。

if ((key.readyOps() & SelectionKey.OP_READ) != 0){
	myBuffer.clear();
	key.channel().read(myBuffer);
	doSomethingWithBuffer(myBuffer.flip());
}

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

if (key.isWritable())

等价于:

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

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

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

SelectionKey对象包含的ready集合与最近一次选择器对所注册的通道所作的检查相同。而每个单独的通道的就绪状态会同时改变。

您可能会从SelectionKey的API中注意到尽管有获取ready集合的方法,但没有重新设置那个集合的成员方法。事实上,您不能直接改变键的ready集合。在下一节里,也就是描述选择过程时,我们将会看到选择器和键是如何进行交互,以提供实时更新的就绪指示的。

让我们试验一下SelectionKey的API中剩下的两个方法:

public abstract class SelectionKey {
	// 这里仅列出部分API
	public final Object attach (Object ob)
	public final Object attachment()
}

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

attach()方法将在键对象中保存所提供的对象的引用。SelectionKey类除了保存它之外,不会将它用于任何其他用途。任何一个之前保存在键中的附件引用都会被替换。可以使用null值来清除附件。可以通过调用attachment()方法来获取与键关联的附件句柄。如果没有附件,或者显式地通过null方法进行过设置,这个方法将返回null

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

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对象是线程安全的,但知道修改interest集合的操作是通过Selector对象进行同步的是很重要的。这可能会导致interestOps()方法的调用会阻塞不确定长的一段时间。选择器所使用的锁策略(例如是否在整个选择过程中保持这些锁)是依赖于具体实现的。幸好,这种多元处理能力被特别地设计为可以使用单线程来管理多个通道。被多个线程使用的选择器也只会在系统特别复杂时产生问题。坦白地说,如果您在多线程中共享选择器时遇到了同步的问题,也许您需要重新思考一下您的设计。

我们已经探讨了SelectionKey的 API,但我们还没有谈完选择键的一切——远远没有。让我们进一步了解如何使用选择器管理键吧。

Java nio入门教程详解(三十四)

  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论