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