Java NIO開發需要注意的陷阱(轉)
轉自出處:http://www.cnblogs.com/pingh/p/3224990.html
陷阱1:處理事件忘記移除key 在select返回值大於0的情況下,迴圈處理 Selector.selectedKeys集合,每處理一個必須從Set中移除
Iterator<SelectionKey> it=set.iterator(); While(it.hasNext()){ SelectionKey key=it.next(); it.remove(); //切記移除 „„處理事件 }
不移除的後果是本次的就緒的key集合下次會再次返回,導致無限迴圈,CPU消耗100%
陷阱2:Selector返回的key集合非執行緒安全
Selector.selectedKeys/keys 返回的集合都是非執行緒安全的 Selector.selectedKeys返回的可移除 Selector.keys 不可變 對selected keys的處理必須單執行緒處理或者適當同步
陷阱3:正確註冊Channel和更新interest 直接註冊不可嗎? channel.register(selector, ops, attachment); 不是不可以,效率問題 至少加兩次鎖,鎖競爭激烈 Channel本身的regLock,競爭幾乎沒有 Selector內部的key集合,競爭激烈 更好的方式:加入緩衝佇列,等待註冊,reactor單執行緒處理
If(isReactorThread()){ channel.register(selector,ops,attachment); } else{ register.offer(newEvent(channel,ops,attachment)); selector.wakeup(); }
同樣,SelectionKey.interest(ops) 在linux上會阻塞,需要獲取selector內部鎖做同步 在win32上不會阻塞 遮蔽平臺差異,避免鎖的激烈競爭,採用類似註冊channel的方式:
if (this.isReactorThread()) { key.interestOps(key.interestOps() | SelectionKey.OP_READ); } else { this.register.offer(new Event(key,SelectionKey.OP_READ)); selector.wakeup(); }
陷阱4:正確處理OP_WRITE OP_WRITE處理不當很容易導致CPU 100% OP_WRITE觸發條件: 前提:interest了OP_WRITE 觸發條件: socket傳送緩衝區可寫 遠端關閉 有錯誤發生 正確的處理方式: 僅在已經連線的channel上註冊 僅在有資料可寫的時候才註冊 觸發之後立即取消註冊,否則會繼續觸發導致迴圈 處理完成後視情況決定是否繼續註冊 沒有完全寫入,繼續註冊 全部寫入,無需註冊
陷阱5:正確取消註冊channel SelectableChannel一旦註冊將一直有效直到明確取消 怎麼取消註冊? channel.close(),內部會呼叫key.cancel() key.cancel(); 中斷channel的讀寫所線上程引起的channel關閉 但是這樣還不夠! key.cancel()僅僅是將key加入cancelledKeys 直到下一次select才真正處理 並且channel的socketfd只有在真正取消註冊後才會close(fd)
後果是什麼? 服務端,問題不大,select呼叫頻繁 客戶端,通常只有一個連線,關閉channel之後,沒有呼叫select就關閉了selector sockfd沒有關閉,停留在CLOSE_WAIT狀態 正確的處理方式,取消註冊也應當作為事件交給reactor處理,及時wakeup做select 適當的時候呼叫selector.selectNow() Netty在超過256連線關閉的時候主動呼叫一次selectNow
static final int CLEANUP_INTERVAL=256; private boolean cleanUpCancelledKeys()throws IOException{ if(cancelledKeys>=CLEANUP_INTERVAL){ cancelledKeys=0; selector.selectNow(); returntrue; } returnfalse; } //channel關閉的時候 channel.socket.close(); cancelledKeys++;
陷阱6:同時註冊OP_ACCPET和OP_READ,同時註冊OP_CONNECT和OP_WRITE 在底層來說,只有兩種事件:read和write Java NIO還引入了OP_ACCEPT和OP_CONNECT OP_ACCEPT、OP_READ == Read OP_CONNECT、OP_WRITE == Write 同時註冊OP_ACCEPT和OP_READ ,或者同時註冊OP_CONNECT和OP_WRITE在不同平臺上產生錯誤的行為,避免這樣做!
陷阱7:正確處理connect SocketChannel.connect方法在非阻塞模式下可能返回false,切記判斷返回值 如果是loopback連線,可能直接返回true,表示連線成功 返回false,後續處理 註冊channel到selector,監聽OP_CONNECT事件 在OP_CONNECT觸發後,呼叫SocketChannel.finishConnect成功後,連線才真正建立 陷阱: 沒有判斷connect返回值 沒有呼叫finishConnect 在OP_CONNECT觸發後,沒有移除OP_CONNECT,導致SelectionKey一直處於就緒狀態,空耗CPU OP_CONNECT只能在還沒有連線的channel上註冊
忠告
儘量不要嘗試實現自己的nio框架,除非有經驗豐富的工程師 儘量使用經過廣泛實踐的開源NIO框架Mina、Netty3、xSocket 儘量使用最新穩定版JDK 遇到問題的時候,也許你可以先看下java的bug database
--------------------- 本文來自 martin_liang 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/martin_liang/article/details/41224503?utm_source=copy