一、更改SelectorKeys集合异常
当线程处于Monitor状态时,说明等待实体的释放。即多个线程在等待同一个锁的释放。
1、对于写线程
当Selector处于工作状态时,是不允许更改其集合体的,即此时不能调用
key.interestOps(key.readyOps())
channel.register(selector,registerOps)
当更改key的读、写时,都涉及到对集合体的变更,此时,必须调用Selector的唤起操作,让selector不处于select()状态,即不处于扫描当前队列的状态。然后再去更改队列里面的集合体。更改好后继续让selector进行select()
selector.wakeup()
2、对于读线程
调用selector的select()操作,当值为0时,
readSelector.select()==0
会等待当前锁的释放
waitSelectiion(inRegInput)
在锁的释放中,首先拿到当前的locker
然后判断locker是否为true,如果为true,证明正正操作某些值。
if(locker.get())
为true,则调用locker的等待操作
locker.get()
等待完成以后,调用locker的唤起操作
locker.notify()
3、这个过程看似是非常正确的,但是有一个致命的漏洞。
比如,每个方块代表一个客户端,蓝色方块代表一个就绪的客户端l(可读、可写或者可读写),灰色代表没有就绪的客户端,selector则不断地从头扫到尾扫描客户端。当从头向尾扫描时,如果有就绪的客户端,会将通道提取出来。其实提取出来的不是通道,提取出来的是selectkeys,也就是说这里面的每一个集合都是selectionkeys。当这些key就绪的时候,会将这些key加入到另外的集合,从而得到当前已经就绪的selectionKeys的集合。当扫描整个队列没有就绪队列的时候,就会重复进行扫描。如果有就绪的key,则会返回有多少个就绪。
如果扫描到第三个的时候,要停止扫描,因为要更改这个集合中的selectionKeys,此时由于进行了唤醒操作selector.wakeUp(),这时会返回0,这样的结果是不正确的。正确方式是,当唤醒并更改selectionKeys之后,再重新进行扫描,返回重写扫描后的结果。
漏洞在于,当扫描到第五个的时候,进行了唤起操作,此时返回的可能是2。为什么当扫描到就绪的key时不直接返回呢?因为至少需要将队列扫描一遍,才会将结果返回。至于在中间将结果返回,是因为强制将其唤醒了。
正确处理方式是,在返回为2的情况下,应该等待当前集合体完成扫描的操作。当扫描完成后,将前面两个消费掉,然后再重头扫描将结果返回。
是否处于更改的状态
AtomicBoolean locker = inRegInput;
else if(locker.get()){
waitSelection(inRegInput);
}
如果处于更改状态,则继续等待。但不需要continue。因为continue是从头从新开始。只是说,当完成当前的等待操作之后,再继续当前的事物处理。
当移除数据后,使用for循环会由于数据移除变更导致出现bug,所以改为迭代器
对于读操作,也同样适用。
二,更改selectionKey状态
更改selector感兴趣的兴趣集合,将其感兴趣的集合移除掉。其实是对队列进行移除操作。所以需要进行同步操作。
调用该方法时,一定不处于select状态,所以不需要改变其locker的true、false值。
当更改selectionkey状态的时候,可能会由于关闭方法调用key.cancel()导致更改集合时发生异常。
三、取消方法
当本线程进行select()操作时,如果调用channel.register()、key.interestOps()、key.clear()都会涉及到对上面结合的变更。
变更无非是移除或者新增的操作,此时应该让selector处于唤醒状态并退出。此时需要更锁的状态,知道当前处于退出状态,需要进行等待。