标签:
最近学习<Java NIO>中的Selector,于是结合Selector的源码,写一点笔记。
讲到Selector,就不能不提SelectableChannel,SelectionKey。它们的关系是:
1. 选择器(Selector)
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。
2. 可选择通道(SelectableChannel)
这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的
通道类的父类。
SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,
那种操作是感兴趣的
注意:FileChannel 对象不是可选择的,因为它们没有继承 SelectableChannel
3. 选择键(SelectionKey)
选 择 键 封 装 了 特 定 的 通 道 与 特 定 的 选 择 器 的 注 册 关 系 。
每个Selector对象维护3个集合(Set),每个集合的元素都是选择键(SelectionKey)。
public abstract Set<SelectionKey> keys();
public abstract Set<SelectionKey> selectedKeys();
b. 想要将一个键直接添加到此集合,不支持!
c. 想从这个集合直接删除某个key,可以调用下面的方法:
java.util.Set.remove(Object o);
//或
java.util.Iterator.remove();
3. ♦ 已取消的键 ♦
已注册的键的集合的子集,这个集合包含了cancel( )方法被调用过的键(这个键已经被无效化),
但它们还没有被注销。
一个键被取消,就会加到这个集合。取消可以通过:
→关闭与键关联的那个通道
→调用键的cancel()方法。
在一个刚初始化的 Selector 对象中,这三个集合都是空的。
从上面的总结看到,对于某些集合的操作为“不支持”的情况,可能让人不解。
!!请注意!!
“不支持”是指“直接”的操作。如我们不能这样做:
//错误代码!!
Set<SelectionKey> keyset = selector.keys(); keyset.clear();
那如何才能“间接”地操作?就需要先了解Selector的工作流程。
1. 打开一个Selector
Selector selector = Selector.open();
//此时selector是新建的(刚初始化),三个集合都是空的。
2. 将通道注册到selector
channel1.register (selector, SelectionKey.OP_READ);
channel2.register (selector, SelectionKey.OP_WRITE);
channel3.register (selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//这里假设3个channel是已存在的
//此时,已注册集合包含3个键。
//已选择集合和已取消集合为空
3. 调用selector.select()
selector.select();
将执行以下检查:
A. 检查已取消的键的集合。如果非空,就删除另外2个集合中在此集合中有的键,
当然相关的通道被注销。还有此集合被清空。
B. 检查已注册的键的集合的键的interest集合。
以上步骤称为“就绪条件”,然后底层操作系统将会根据刚才的条件去查询,
来确定每个通道所关心的操作的真实就绪状态。
在确定通道就绪状态时:
A. 对于还没准备好的通道,将不会执行任何操作。
B. 对于那些至少已经准备好interest集合中的一种操作的通道,将会执行以下2种操作的一种:
B1. 如果通道的键还没有处于已选择的键的集合,那么键的ready集合将被清空,然后表示
操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。
B2. 如果键在已选择的键的集合中,键的 ready 集合将被表示操作系统发现的当前已经准备
好的操作的比特掩码更新
下面是我画的示意图:
左图为Selector.select()执行前:
a. Channel注册到Selector,所以Selector的Keys集合中包含 Selection Key(记为K)。而K中指明了
Channel为C 和Selector为S之间的注册关系。
b. K的Interest Ops值为Read,表示Channel对读操作感兴趣。请留意:此时,S中Selected Keys集合为空。
右图为Selector.select()执行后:
K的Read Ops值为Read,表示Channel的读操作就绪。
K被加到S中Selected Keys集合。
这里我补充2点:
* selector.select()是系统调用。
* 在select()调用后,才去改变“就绪条件”,是不会影响本次select()的结果。
select()会使调用线程阻塞,当有下面的条件会返回:
* 一个或以上的通道就绪
* 此select.wakeup()方法的调用
* 调用线程被中断(interrupted)
select()是有返回值的,只是返回的不是已选择的键的集合中键的个数,
而是在此次调用select()时,ready集合被改变的键的数目。
4. 取得selector的selected keys
第3步的select()方法执行后,Selector的Selected Keys被更新。然后我们就遍历这个集合。
前面说过,获取这个集合可以用 selectKeys( )方法。
遍历这个集合时,对每个键进行处理,然后将键从这个集合删除。
删除这一步很重要:
因为Selector把Selection Key添加到Selected Keys集合后,它就不会删除这个Key。
需要我们手动去删除,相当于我们告诉Selector,“在遍历时我们已经看到并处理完这个Key
所指示的readyOps”,于是Selector就知道这个key所关联的那个通道上,相关的就绪条件已经
没用了(因为我们处理完了),它就在下一次select()操作而这个key所关联的那个通道有新到来的
就绪条件,就把key的readyOps清空并重新设置为反映新状态的值。
标签:
原文地址:http://www.cnblogs.com/JHChen/p/5372087.html