Java NIO學習筆記四 NIO選擇器

Java NIO選擇器

  A Selector是一個Java NIO組件,能夠檢查一個或多個NIO通道,並肯定哪些通道已準備就緒,例如讀取或寫入。這樣一個線程能夠管理多個通道,從而管理多個網絡鏈接。服務器

爲何選擇器?

  使用單個線程來處理多個通道的優勢是您須要較少的線程來處理通道。你可使用一個線程來處理你全部的頻道。線程之間的切換消耗系統資源較大,每一個線程也佔用操做系統中的一些資源(內存)。因此你使用的線程越少越好。網絡

  現代操做系統和CPU在多任務處理中變得愈來愈好,因此隨着時間的推移,多線程的開銷愈來愈小。事實上,若是CPU具備多個內核,處理少許任務時會浪費CPU電源。多線程

  這是一個線程使用Selector來管理3個通道的流程圖:測試

Java NIO:選擇器
Java NIO:A Thread uses a Selector to handle 3 Channel's

建立選擇器

  Selector經過調用Selector.open()方法 建立一個,以下所示:spa

Selector selector= Selector.open();

爲選擇器註冊通道

  爲了使用一個ChannelSelector你必須註冊ChannelSelector。這是使用SelectableChannel的register()方法完成的 ,以下所示:操作系統

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,SelectionKey.OP_READ);

  Channel必須在非阻塞模式與Selector使用。這意味着你不能將FileChannelSelector一塊兒使用,由於 FileChannel不能切換到非阻塞模式。Socket channels能夠正常工做。線程

  注意register()方法的第二個參數。這是一個「興趣集」,意思是你能夠經過Selector在通道監聽你須要的事件,。你能夠監聽四種不一樣的事件:rest

  • Connect
  • Accept
  • Read
  • Write

  「第一事件」的頻道也被稱爲「ready」的事件。所以,已成功鏈接到其餘服務器的通道是「connect ready」。接受傳入鏈接的服務器套接字通道是「Accept」ready。具備準備讀取的數據的通道「read」ready。準備好向您寫入數據的通道是「write」ready。code

這四個事件由四個SelectionKey常量表示:對象

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

若是您對多個事件感興趣,能夠將常數在一塊兒使用,以下所示:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 

SelectionKey‘s

  當您Channel使用Selector 該register()方法註冊時返回一個SelectionKey對象。此SelectionKey對象包含一些有趣的屬性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

The interest set   ------  興趣集

  興趣集是您感興趣的「選擇」事件集。能夠經過如下方式讀取和寫入該興趣集SelectionKey

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept = interestSet&SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet&SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet&SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet&SelectionKey.OP_WRITE; 

  能夠看到將設置給定SelectionKey常數的選擇興趣集,以肯定某個事件是否在興趣集中。

The ready set      -----就緒集

  準備集是通道準備好的一組操做。您將主要在選擇後訪問就緒集。選擇在後面的部分進行說明。

  訪問就緒集代碼展現:

int readySet = selectionKey.readyOps();

  您能夠以與興趣集相同的方式測試,頻道準備就緒的事件/操做。可是,您也可使用這四種方法,這些方法都返回布爾值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

 Channel + Selector      ----頻道+選擇器

  訪問通道+選擇器SelectionKey是不重要的。

   具體操做以下:

Channel channel = selectionKey.channel();
Selector selector= selectionKey.selector();

 attached object (optional)---附加對象

  您能夠附加一個對象,SelectionKey這是一種方便識別給定頻道或將更多信息附加到頻道的方法。例如,您能夠附加Buffer您正在使用的頻道,或者包含更多聚合數據的對象。如下是附加對象的方法:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

  您也能夠附上已經一個對象,而註冊Channel用 Selector,在register()方法。這是怎麼樣的樣子:

SelectionKey key = channel.register(selector,SelectionKey.OP_READ,theObject);

經過選擇器選擇頻道

  一旦您註冊了一個或多個渠道,Selector您能夠調用其中一種select()方法。這些方法返回對您感興趣的事件(鏈接,接受,讀取或寫入)「準備好」的通道。換句話說,若是您對準備閱讀的頻道感興趣,您將收到準備從select()方法中閱讀的頻道。

如下是select()方法:

  • int select()
  • int select(長時間超時)
  • int selectNow()

  select() 阻止至少一個通道爲您註冊的事件準備好。

  select(long timeout)select()除了它阻塞最多timeout毫秒(參數)外,仍是同樣的。

  selectNow()根本不阻止 它當即返回任何通道準備就緒。

  在int由返回的select()方法告訴不少渠道如何準備。也就是說,自從你上次打電話以來,已經有多少個頻道準備好了select()。若是您打電話select(),而且返回1,由於一個通道已經準備就緒,而且再次撥打select()一個通道,而且還有一個通道已經準備就緒,它將再次返回1。若是您準備好的第一個頻道沒有任何功能,您如今能夠有2個準備好的頻道,但每一個select()通話之間只有一個頻道已經準備就緒。

selectedKeys()

  一旦您調用了一種select()方法,其返回值表示一個或多個通道已準備好,您能夠經過調用選擇器selectedKeys()方法經過「選定的鍵集」訪問就緒通道。這是怎麼樣的樣子:

設置<SelectionKey> selectedKeys = selector.selectedKeys();    

  當你註冊一個通道SelectorChannel.register()方法返回一個SelectionKey對象。該鍵表示通道與該選擇器的註冊。這些鍵能夠經過該selectedKeySet()方法訪問。從 SelectionKey

您能夠迭代此選擇的密鑰集以訪問就緒通道。這是怎麼樣的樣子:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()){
    
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()){
        //鏈接被ServerSocketChannel接受。

    } else if(key.isConnectable()){
        //與遠程服務器創建鏈接。

    } else if(key.isReadable()){
        //一個頻道準備好閱讀

    } else if(key.isWritable()){
        //一個頻道準備好寫做了
    }

    keyIterator.remove();
}

  該循環遍歷所選密鑰集中的密鑰。對於每一個密鑰,它測試密鑰以肯定密鑰引用的信道準備好了。

  請注意keyIterator.remove()每次迭代結束時的呼叫。在 Selector不刪除SelectionKey從爲本身選擇的關鍵實例。當您完成處理頻道後,您必須這樣作。下一次通道變爲「就緒」時,Selector將再次將其添加到所選的鍵集。

  該SelectionKey.channel()方法返回的通道應該被轉換爲您須要使用的通道,例如ServerSocketChannel或SocketChannel等。

wakeup()方法

  調用select()被阻止的方法的線程select(),即便沒有通道還沒有準備就能夠離開該方法。這是由具備不一樣的線程調用完成Selector.wakeup() 的方法,Selector其中第一線呼籲 select()上。線程等待內線select()而後當即返回。

  若是一個不一樣的線程調用wakeup(),而且當前沒有線程被阻塞select(),那麼調用的下一個線程select()將當即「喚醒」。

close()方法

  當你完成Selector你調用它的close() 方法。這關閉並使所Selector註冊的全部SelectionKey 實例無效Selector。渠道自己沒有關閉。

全選擇器示例

  這是一個打開一個完整的例子Selector,註冊一個通道(通道實例化被忽略),而且繼續監視Selector 四個事件(接受,鏈接,讀取和寫入)的「準備」。

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,SelectionKey.OP_READ);


whiletrue){

  int readyChannels = selector.select();

  if(readyChannels == 0)繼續;


  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()){

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()){
        //鏈接被ServerSocketChannel接受。

    } else if(key.isConnectable()){
        //與遠程服務器創建鏈接。

    } else if(key.isReadable()){
        //一個頻道準備好閱讀

    } else if(key.isWritable()){
        //一個頻道準備好寫做了
    }

    keyIterator.remove();
  }
}
相關文章
相關標籤/搜索