NIO-Selector類詳解

Selector自己爲抽象類,AbstractSelector是Selector類的抽象實現類,具體的實現類更加底層(SelectorImpl,位於sun.nio.ch);Selector即爲"選擇器",支撐了NIO的多路複用.Selector不能直接建立,須要經過Selector.open()得到,該方法將使用系統默認的選擇器提供者建立新的選擇器(SelectorProvider),能夠經過選擇器的close方法關閉它.javascript

經過SelectionKey對象來表示可選擇通道(SelectableChannel)到選擇器的註冊.SelectionKey由Selector維護.html

一般一個Channel只會被註冊到一個Selector,一個Selector能夠「監測」多個Channel;事實上Channel能夠註冊在任意一個Selector上,ServerSocketChannel和SocketChannel能夠共用一個Selector,也能夠各自使用不一樣的。java

 

一.Selector維護三種選擇鍵:數組

  1.  keys:保存了全部已註冊且沒有cancel的選擇鍵,Set類型.能夠經過selector.keys()獲取
  2. selectedKeys:已選擇鍵集,即前一次操做期間,已經準備就緒的通道所對應的選擇鍵.此集合爲keys的子集.經過selector.selectedKeys()獲取.
  3. canceledKeys:已取消鍵.已經被取消但還沒有取消註冊(deregister)的選擇鍵.此集合不可被訪問.爲keys()的子集.

對於新的Selector實例,上述三個集合均爲空.經過channel.register將致使keys()集合被添加.若是某個selectionKey.cancel()被調用,那麼此key將會被添加到canceldKey集合中,在下一次selector選擇期間,若是canceldKeys不爲空,將會致使觸發此key的deregister操做(釋放資源,並從keys中移除).不管經過channel.close()仍是經過selectionKey.cancel(),都會致使key被加入到cannceldKey中.安全

 

每次選擇操做期間(select),均可以將key添加到selectedKeys中或者將從canceledKey中移除.負責"選擇"key的操做爲select(),selectNow(),select(long timeout);"選擇操做"執行的步驟:網絡

  1. 首先對cancelledKey進行清除,遍歷"已取消鍵集合",並對每一個key進行deregister操做,而後從"已取消鍵"集合中刪除.從keys集合中刪除,從"已選擇鍵"中刪除.
  2. 將由更底層的操做來查詢操做系統中每一個channel的 準備就緒信息.若是該通道的key還沒有在selectedKeys中存在,則將其添加到該集合中.若是該通道的key已經存在selectedKeys中,則修改器opts事件集."選擇"通道的就緒信息,在底層是由"可擴展尺寸"的線程池執行,可是在併發較高的IO操做中,這仍然會存在"select"延遲問題.
  3. "選擇"操做結束後,再次執行1),已防止在"選擇"期間,有些keys被取消.

select是個被同步的過程(將對keys,selectedKey都會同步).多線程調用會被阻塞.多線程

二.併發性:併發

Selector能夠在多線程環境中使用,可是其各類鍵集合並不是是線程安全的.app

Selector自己對各類key集合的操做,都是同步的,固然爲了不死鎖問題,其同步的順序也是一致的.好比在執行select操做其餘,其餘線程register,將會阻塞.能夠在任意時刻關閉通道或者取消鍵.由於select操做並未對cancelledKey同步,所以有可能再selectedKey中出現的key是已經被取消的.這一點須要注意.須要校驗:key.isValid() && key.isReadable()...異步

 

三.select()和select(long)是阻塞操做:

  • 經過selector.wakeup()能夠將其返回
  • 經過調用selector.close()
  • 調用已阻塞線程的interrupt方法.

注意selector.close()方法也是對keys和selectedKeys進行了同步.通常狀況下,keys()和selectedKeys在多線程環境中使用是不安全的.

事實上selector.keys()返回的結果是一個Collections.unmodifiableSet(keys),其中keys參數爲selector內部維持的集合.因而可知keys集合是隻讀的.

selector.selectedKeys()返回的結果是一個Util.ungrowableSet(selectedKeys),其中參數爲selector內部維持的集合.因而可知selectedKeys是沒法被外部進行add操做的,

可是能夠remove以及進行iterator操做.

(參見sun.nio.ch.SelectorImpl,源碼來自http://javasourcecode.org/html/open-source/jdk/jdk-6u23/sun/nio/ch/SelectorImpl.java.html)

 

四.方法列表:

  •  public static Selector open() throws IOException:打開一個選擇器,此操做將會致使系統默認的SelectorProvider對象的openSelector方法來建立選擇器.(此後介紹在各個OS下provider特性)
  • public abstract boolean isOpen():檢測此selector是否處於打開狀態,僅當selector建立成功卻沒有關閉,返回true.
  • public abstract Selt<SelectionKey> keys():獲取此selector中已經註冊(可能已經cancelled但還沒有deregister)的全部選擇鍵.此集合不能被外部修改.
  • public abstract Set<SelectionKey> selectedKeys():返回此選擇器當前已選擇鍵集合.此集合不能被add,但能夠remove操做.
  • public abstract int selectNow() throws IOException:"選擇"操做,獲取相應的通道已爲I/O操做就緒.此方法爲非阻塞操做,當即返回已經就緒的的個數.可能爲0."非阻塞",意味着,在I/O通道就緒信息的檢測上,是無阻塞的,即若是底層線程(底層實現爲多線程輪詢檢測I/O操做,並將已就緒的key放在內部的隊列中)所維持的"就緒通道"個數爲任意數字都當即返回,固然包括0.由於同一個selector實例中,select(),selectNow(),select(long)底層的實現幾乎一致,方法實體中都會對keys和selectedKeys進行同步操做,所以在多線程中同時進行"select"調用,也將會存在互相阻塞的影響.
  • public abstract int select(long timeout) throws IOException:選擇一組鍵,此方法爲處於阻塞模式的選擇操做.盡在至少一個通道就緒/調用選擇器的wakeup/當前線程被interrupt/超時後返回.
  • public abstract int select() throws Exception選擇一組鍵,此方法爲阻塞模式的選擇操做,直到至少一個通道就緒/wakeup/interrupt時返回.注意Selector支持interrupt(即具備interruptable),它採起了和InterruptableChannel相似的設計思路.即具備begin()/end().

Java代碼 

 收藏代碼

  1. //僞代碼以下:  
  2. doSelect(){  
  3.     try{  
  4.         begin();//註冊中斷操做  
  5.         select();  
  6.     }finaly{  
  7.         end();//解除中斷和檢測中斷.  
  8.     }  
  9. }  

 

在實際底層I/O選擇時(阻塞行爲),在方法開始前執行begin():

begin所作的事情,就是向Thread註冊interruptable對象,參見AbstractSelector.begin();

Java代碼 

  1. if (interruptor == null) {  
  2.    interruptor = new Interruptible() {  
  3.    public void interrupt() {  
  4.         AbstractSelector.this.wakeup();//中斷回調,主要操做爲讓當前selector.wakeup(),即在阻塞中返回.  
  5.    }};  
  6. }  
  7. //註冊thread.blocker,向Thread.currentThread()中Interruptable blocker屬性賦值.以便在thread.interrupt()時可以執行回調.  
  8. AbstractInterruptibleChannel.blockedOn(interruptor);  
  9. //檢測當前thread是否已經被終端.  
  10. if (Thread.currentThread().isInterrupted())  
  11.    interruptor.interrupt();  
  12. }  

 

Java代碼 

  1. //Thread.interrupt()方法對異步中斷的相應操做  
  2. public void interrupt() {  
  3.     if (this != Thread.currentThread())  
  4.         checkAccess();  
  5.   
  6.     synchronized (blockerLock) {  
  7.         Interruptible b = blocker;  
  8.         if (b != null) {  
  9.         interrupt0();       // Just to set the interrupt flag  
  10.         b.interrupt();  
  11.         return;  
  12.         }  
  13.     }  
  14.     interrupt0();  
  15. }  

 

 在實際IO選擇返回後,以及異常將會執行end(),end()方法要比Channel的控制簡單,直接取消blocker = null.

 如今,你知道Selector中斷和喚醒的機制了嗎?

  • public abstract Selector wakeup():使還沒有返回的選擇操做當即返回.若是另外一個線程目前正阻塞在select()或者select(long)方法的調用中,則該調用將當即返回.返回後,再次調用select等,將會有可能繼續阻塞.屢次連續的調用wakeup,效果一致.執行wakeup,將會致使底層Selector實現類的實例的interrupt屬性標記爲true,而後由JNI調用觸發pipe被打斷併發回(pipe爲selector內部機制,稍後介紹),此後全部在"select"操做上阻塞的線程,依次獲取keys和selectedKeys鎖,此後將對interrupt屬性校驗,若是interrupt = true,將會重置pipe(本身給本身創建一個pipe-socket連接),而後再次將interrupt置爲false,並從阻塞中返回.因而可知,若是多個線程阻塞,事實上wakeup只能讓正在阻塞的一個線程返回.阻塞在select操做上,有2中狀況:1)由於keys集合同步阻塞 2) 由於selector IO阻塞...wakeup()方法是讓基於pipe IO阻塞的操做返回.可是由於keys同步鎖的阻塞它將無能爲力.wakeup是一種對底層操做消耗較爲嚴重的操做,須要對此操做的調用頻度有所關心.
  • public abstract void close() throws Exception:關閉選擇器.若是有其餘線程阻塞在此selector的選擇操做中,則中斷該線程.close()方法內部執行順序爲:
  1. 調用wakeup()方法,使阻塞的線程當即返回.
  2. 關閉selector所關聯的底層pipe鏈接信息.
  3. 對keys和selectedKeys同步,而後遍歷此selector所註冊是全部的channel(即選擇鍵,底層而言每一個channel對應一個選擇鍵),並依次執行selector.degregister(key),deregister操做將致使每一個channel都刪除本身維持的當前selector引用(從內部的key[]數組中刪除).若是當前channel已經關閉,則直接銷燬當前channel所關聯的全部資源(好比關閉打開的文件描述),若是當前channel爲open,則保持資源.
  4. 退出全部的底層"select"事件線程池.

 

五.SelectionKey(選擇鍵)

抽象類,表示selectableChannel在Selector中註冊的標識.每一個Channel向Selector註冊時,都將會建立一個selectionKey.選擇鍵將Channel與Selector創建了關係,並維護了channel事件.能夠經過cancel方法取消鍵,取消的鍵不會當即從selector中移除,而是添加到cancelledKeys中,在下一次select操做時移除它.因此在調用某個key時,須要使用isValid進行校驗.

選擇鍵包含兩個操做集,操做集爲位運算值,每一位表示一種操做.

  1. interest 集合:當前channel感興趣的操做,此類操做將會在下一次選擇器select操做時被交付,能夠經過selectionKey.interestOps(int)進行修改.
  2. ready 集合:表示此選擇鍵上,已經就緒的操做.每次select時,選擇器都會對ready集合進行更新;外部程序沒法修改此集合.

操做集:

OP_ACCEPT:鏈接可接受操做,僅ServerSocketChannel支持

OP_CONNECT:鏈接操做,Client端支持的一種操做

OP_READ/OP_WRITE

 

這些opts都不爲0,若是向selector之中register一個爲「0」的opts,表示此channel不關注任何類型的事件。(言外之意,register方法只是獲取一個selectionKey,具體這個Channel對何種事件感興趣,能夠在稍後操做)

 

方法列表:

  • public abstract SelectableChannel channel():返回此選擇鍵所關聯的通道.即便此key已經被取消,仍然會返回.
  • public abstract Selector selector():返回此選擇鍵所關聯的選擇器,即便此鍵已經被取消,仍然會返回.
  • public abstract boolean isValid():檢測此key是否有效.當key被取消,或者通道被關閉,或者selector被關閉,都將致使此key無效.在AbstractSelector.removeKey(key)中,會致使selectionKey被置爲無效.
  • public abstract void cancel():請求將此鍵取消註冊.一旦返回成功,那麼該鍵就是無效的,被添加到selector的cancelledKeys中.cancel操做將key的valid屬性置爲false,並執行selector.cancel(key)(即將key加入cancelledkey集合)
  • public abstract int interesOps():得到此鍵的interes集合.
  • public abstract SelectionKey interestOps(int ops):將此鍵的interst設置爲指定值.此操做會對ops和channel.validOps進行校驗.若是此ops不會當前channel支持,將拋出異常.
  • public abstract int readyOps():獲取此鍵上ready操做集合.即在當前通道上已經就緒的事件.
  • public final boolean isReadable(): 檢測此鍵是否爲"read"事件.等效於:k.,readyOps() & OP_READ != 0;還有isWritable(),isConnectable(),isAcceptable()
  • public final Object attach(Object ob):將給定的對象做爲附件添加到此key上.在key有效期間,附件能夠在多個ops事件中傳遞.
  • public final Object attachment():獲取附件.一個channel的附件,能夠再當前Channel(或者說是SelectionKey)生命週期中共享,可是attachment數據不會做爲socket數據在網絡中傳輸.
相關文章
相關標籤/搜索