Selector是Java NIO中的一個組件,用於檢查一個或多個NIO Channel的狀態是否處於可讀、可寫。如此能夠實現單線程管理多個channels,也就是能夠管理多個網絡連接。網絡
用單線程處理多個channels的好處是我須要更少的線程來處理channel。實際上,你甚至能夠用一個線程來處理全部的channels。從操做系統的角度來看,切換線程開銷是比較昂貴的,而且每一個線程都須要佔用系統資源,所以暫用線程越少越好。多線程
須要留意的是,現代操做系統和CPU在多任務處理上已經變得愈來愈好,因此多線程帶來的影響也愈來愈小。若是一個CPU是多核的,若是不執行多任務反而是浪費了機器的性能。不過這些設計討論是另外的話題了。簡而言之,經過Selector咱們能夠實現單線程操做多個channel。性能
這有一幅示意圖,描述了單線程處理三個channel的狀況:spa
Java NIO: A Thread uses a Selector to handle 3 Channel's操作系統
建立一個Selector能夠經過Selector.open()方法:線程
Selector selector = Selector.open();
爲了同Selector掛了Channel,咱們必須先把Channel註冊到Selector上,這個操做使用SelectableChannel。register():設計
channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Channel必須是非阻塞的。因此FileChannel不適用Selector,由於FileChannel不能切換爲非阻塞模式。Socket channel能夠正常使用。rest
注意register的第二個參數,這個參數是一個「關注集合」,表明咱們關注的channel狀態,有四種基礎類型可供監聽:code
一個channel觸發了一個事件也可視做該事件處於就緒狀態。所以當channel與server鏈接成功後,那麼就是「鏈接就緒」狀態。server channel接收請求鏈接時處於「可鏈接就緒」狀態。channel有數據可讀時處於「讀就緒」狀態。channel能夠進行數據寫入時處於「寫就緒」狀態。server
上述的四種就緒狀態用SelectionKey中的常量表示以下:
若是對多個事件感興趣可利用位的或運算結合多個常量,好比:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
在上一小節中,咱們利用register方法把Channel註冊到了Selectors上,這個方法的返回值是SelectionKeys,這個返回的對象包含了一些比較有價值的屬性:
這5個屬性都表明什麼含義呢?下面會一一介紹。
這個「關注集合」實際上就是咱們但願處理的事件的集合,它的值就是註冊時傳入的參數,咱們能夠用按爲與運算把每一個事件取出來:
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;
"就緒集合"中的值是當前channel處於就緒的值,通常來講在調用了select方法後都會須要用到就緒狀態,select的介紹在鬍鬚文章中繼續展開。
int readySet = selectionKey.readyOps();
從「就緒集合」中取值的操做相似月「關注集合」的操做,固然還有更簡單的方法,SelectionKey提供了一系列返回值爲boolean的的方法:
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
從SelectionKey操做Channel和Selector很是簡單:
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
咱們能夠給一個SelectionKey附加一個Object,這樣作一方面能夠方便咱們識別某個特定的channel,同時也增長了channel相關的附加信息。例如,能夠把用於channel的buffer附加到SelectionKey上:
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();
附加對象的操做也能夠在register的時候就執行:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
一旦咱們向Selector註冊了一個或多個channel後,就能夠調用select來獲取channel。select方法會返回全部處於就緒狀態的channel。 select方法具體以下:
select()方法在返回channel以前處於阻塞狀態。 select(long timeout)和select作的事同樣,不過他的阻塞有一個超時限制。
selectNow()不會阻塞,根據當前狀態馬上返回合適的channel。
select()方法的返回值是一個int整形,表明有多少channel處於就緒了。也就是自上一次select後有多少channel進入就緒。舉例來講,假設第一次調用select時正好有一個channel就緒,那麼返回值是1,而且對這個channel作任何處理,接着再次調用select,此時剛好又有一個新的channel就緒,那麼返回值仍是1,如今咱們一共有兩個channel處於就緒,可是在每次調用select時只有一個channel是就緒的。
在調用select並返回了有channel就緒以後,能夠經過選中的key集合來獲取channel,這個操做經過調用selectedKeys()方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
還記得在register時的操做吧,咱們register後的返回值就是SelectionKey實例,也就是咱們如今經過selectedKeys()方法所返回的SelectionKey。
遍歷這些SelectionKey能夠經過以下方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }
上述循環會迭代key集合,針對每一個key咱們單獨判斷他是處於何種就緒狀態。
注意keyIterater.remove()方法的調用,Selector自己並不會移除SelectionKey對象,這個操做須要咱們收到執行。當下次channel處於就緒是,Selector任然會吧這些key再次加入進來。
SelectionKey.channel返回的channel實例須要強轉爲咱們實際使用的具體的channel類型,例如ServerSocketChannel或SocketChannel.
y因爲調用select而被阻塞的線程,能夠經過調用Selector.wakeup()來喚醒即使此時已然沒有channel處於就緒狀態。具體操做是,在另一個線程調用wakeup,被阻塞與select方法的線程就會馬上返回。
當操做Selector完畢後,須要調用close方法。close的調用會關閉Selector並使相關的SelectionKey都無效。channel自己無論被關閉。
這有一個完整的案例,首先打開一個Selector,而後註冊channel,最後錦亭Selector的狀態:
Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.select(); if(readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); } }