問題
今天調試NIO後臺發現一個蛋疼的問題。經過調試之後,出現問題的流程描述如下:
客戶端向服務端發送消息,服務端使用IO多路複用處理輸入,即一個selector監聽多個channel。第一條消息正常接收,發送第二條消息時,select()立即返回0,然後開始continue無限循環,導致第二條消息無法正常處理。
下方是服務端處理select()的代碼(由一個異步線程處理)即read監聽線程
while (!isClosed.get()) {
if (readSelector.select() == 0) {
//這裏有一個等待操作,等待註冊結束
waitSelection(inRegInput);
continue;
}
Iterator<SelectionKey> iterator = readSelector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isValid()) {
// 取消繼續對keyOps的監聽
key.interestOps(key.readyOps() & ~SelectionKey.OP_READ);
//線程池執行read操作
inputHandlePool.execute(new InputHandlerImpl(key));
}
}
}
按理說,有新的channel就緒時,select()會返回大於0的數,或者沒有channel就緒,select()也是保持阻塞狀態,不會有返回。可現在問題是,爲什麼它立即返回0了?
原因
在Stack Overflow上找到了答案:
Java NIO Selector select() returns 0 although channels are ready
其實select()是否返回與selectedKeys集合有關。當selectedKeys集合不爲空時,select()會立即返回,但是其返回值是發生改變的keys數量,即新的就緒通道數量,這裏不可能是1。因此我的這個場景下,第一條消息會產生一個新的key,我處理完沒有將其刪除,所以收第二條消息時,認定這個key沒有發生改變,就會導致select()返回0,從而導致無限循環。所以,一定要把key從selectedKeys集合中移除。
解決方法:很簡單,遍歷迭代器的循環中加一句iterator.remove()移除已處理的key。代碼如下:
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
......
}