1. 程式人生 > >Java NIO開發需要注意的陷阱(轉)

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