java NIO之Selector select操作: 关于多线程同步

    Selector是java NIO的核心,通过Selector实现非阻塞式IO。一个Selector可以对应多个不同类型的Channel,并且以SelectedKey进行标记管理。换句话说,Selector只管理SelectedKey而直接管理Channel。

    Selector对象中有三个集合,分别是key set,selected-key set和cancelled-key set。key set就是Selector中所有注册了的Channel的代表,每当selector注册进一个Channel,就会在key set中增加一个SelectedKey;Selected-key set是key set的一个子集,存放的是准备好至少一项操作(accpt,connect,write,read)的key;cancelled-key set则是存放下一次select操作时会被移除的key,也是key set的一个子集。key set可以手动移除,但是不能直接增加,移除时不会直接移除,而是标记到cancelled-key set中,准备下次select操作时移除。

select操作引发selected-key set的增删,以及引发key set和cancelled-key set的删除。select操作有select(), select(long), and selectNow()几个,每个select操作分三步:

1.从所有set中移除cancelled-key set中的key,清空cancelled-key set;

2.询问底层的操作系统,key set中剩下的key是否已经准备好它们的interest set中指明的操作,如果有至少一项操作准备好,则分两种情况:

1)如果该key原来不在selected-key set,则先清空该key原来的ready- set,并把已经就绪的操作标记放入到该key新的ready-set中,并且将该key放入到selected-key set中。

2)如果该key原来就已经在selected-key set中,则更新该key的ready- set以标记新的查询到的就绪操作,并且保留原来的标记。

如果某个key的interest-set为空,则不询问操作系统,也就没有后续的步骤了。

3.如果在步骤2进行的过程中有key被加入到了cancelled-key set中,则执行步骤1的操作。

IO阻塞与否,就是在select操作中表现出来的。

关于多线程同步

selector本身是线程安全的,但是它的set不是同步set,所以如果直接操作set,则不能保证线程安全,如果要直接操作各个set,需要客户代码对set本身实施同步策略。select操作对selector 自身, key set, selected-key set做了同步措施,对cancelled-key set的1、3步操作也是同步的。

因为key的cancel以及channel的关闭随时都有可能发生,而他们的状态只能在下一次select操作的时候才能够被监测到并更新,所以,应用程序代码要注意多线程同步问题,避免其它线程去改变channel或者key的状态(如关闭一个channel),否则通过Selector的selectedKeys方法返回的selected-key set中的key,可能也是不可用的,因为selectedKeys只能保证在调用的时候key是就绪的,但是不保证之后该key没有被修改(比如channel被关闭)。