并发客户端BUG修复与性能优化一

一、更改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处于唤醒状态并退出。此时需要更锁的状态,知道当前处于退出状态,需要进行等待。

发布了174 篇原创文章 · 获赞 115 · 访问量 83万+

猜你喜欢

转载自blog.csdn.net/nicolelili1/article/details/105130942