併發客戶端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處於喚醒狀態並退出。此時需要更鎖的狀態,知道當前處於退出狀態,需要進行等待。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章