轉自:https://blog.csdn.net/robinjwong/article/details/41792623
- java.nio.channels
- public abstract class Selector extends Object implements Closeable
Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準備的元件。這樣,一個單獨的線程可以管理多個channel,進而管理多個網絡連接配接。
NIO的通訊過程:

使用Selector
僅用單個線程來處理多個Channels的好處是,隻需要更少的新城來處理通道。事實上,可以隻用一個線程處理所有的通道。
Selector的建立
通過調用Selector.open()方法建立一個Selector,
Selector selector = Selector.open();
- isOpen() —— 判斷Selector是否處于打開狀态。Selector對象建立後就處于打開狀态了
- close() —— 當調用了Selector對象的close()方法,就進入關閉狀态.。用完Selector後調用其close()方法會關閉該Selector,且使注冊到該Selector上的所有SelectionKey執行個體無效。通道本身并不會關閉
向Selector注冊通道
為了将Channel和Selector配合使用,必須将channel注冊到selector上。
通過SelectableChannel。register()方法來實作。
- channel.configureBlocking(false);
- SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
與Selector一起使用時,Channel必須處于非阻塞模式下。
這意味着FIleChannel與Selector不能一起使用。
注意register()方法的第二個參數,這是一個”interest集合“,意思是在通過Selector監聽Channel時對什麼事件感興趣。
可以監聽四種不同類型的事件:
- Connect
- Accept
- Read
- Write
通道觸發了一個事件意思是該事件已經就緒。是以,某個channel成功連接配接到另一個伺服器稱為”連接配接就緒“。一個server socket channel準備号接收新進入的連接配接稱為”接收就緒“。一個有資料可讀的通道可以說是”讀就緒“。等代寫資料的通道可以說是”寫就緒“。
這四種事件用SelectionKey的四個常量來表示:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
register()傳回值 —— SelectionKey, Selector中的SelectionKey集合
隻要ServerSocketChannel及SocketChannel向Selector注冊了特定的事件,Selector就會監控這些事件是否發生。
SelectableChannel的register()方法傳回一個SelectionKey對象,該對象是用于跟蹤這些被注冊事件的句柄。
一個Selector對象會包含3種類型的SelectionKey集合:
- all-keys集合 —— 目前所有向Selector注冊的SelectionKey的集合,Selector的keys()方法傳回該集合
- selected-keys集合 —— 相關事件已經被Selector捕獲的SelectionKey的集合,Selector的selectedKeys()方法傳回該集合
- cancelled-keys集合 —— 已經被取消的SelectionKey的集合,Selector沒有提供通路這種集合的方法
當register()方法執行時,建立一個SelectioKey,并把它加入Selector的all-keys集合中。
如果關閉了與SelectionKey對象關聯的Channel對象,或者調用了SelectionKey對象的cancel方法,這個SelectionKey對象就會被加入到cancelled-keys集合中,表示這個SelectionKey對象已經被取消。
在執行Selector的select()方法時,如果與SelectionKey相關的事件發生了,這個SelectionKey就被加入到selected-keys集合中,程式直接調用selected-keys集合的remove()犯法,或者調用它的iterator的remove()方法,都可以從selected-keys集合中删除一個SelectionKey對象。
SelectionKey
表示SelectableChannel 在 Selector 中的注冊的标記/句柄。
register()方法傳回一個SelectinKey對象,這個對象包含一些你感興趣的屬性:
- interest集合
- ready集合
- Channel
- Selector
- 附加的對象
通過調用某個SelectionKey的cancel()方法,關閉其通道,或者通過關閉其選擇器來取消該Key之前,它一直保持有效。
取消某個Key之後不會立即從Selector中移除它,相反,會将該Key添加到Selector的已取消key set,以便在下一次進行選擇操作的時候移除它。
- interest集合 —— 感興趣的事件集合,可以通過SelectionKey讀寫interest集合,
- int interestSet = selectionKey.interestOps();
- boolean isInterestedInAccept = (interestSet & Selection.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
- boolean isInterestedInConnect = interestSet & SelectioKey.OP_CONNECT;
- boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
- boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
- ready集合 —— 是通道已經準備就緒的操作的集合,在一個選擇後,你會是首先通路這個ready set,
int readySet = selectionKey.readyOps();
可以向檢測interet集合那樣的方法,來檢測channel中什麼事件或操作已經就緒,也可以使用一下四個方法,
- selectionKey.isAcceptable();
- selectionKey.isConnectable();
- selectionKey.isReadable();
- selectionKey.isWritable();
- 從SelectionKey方位Channel和Selector:
- Channel channel = selectionKey.channel();
- Selector selector = selectionKey.selector();
- 附加的對象 —— 可以将一個對象或者更多的資訊附着到SelectionKey上,這樣就能友善的識别某個給定的通道。例如,可以附加與通道一起使用的Buffer,或是包含聚集資料的某個對象,
- selectionKey.attach(theObject);
- Object attachedObj = selectionKey.attachment();
通過Selector選擇就緒的通道
一旦向Selector注冊了一個或多個通道,就可以調用幾個重載的select()方法。
這些方法傳回你所感興趣的事件(連接配接,接受,讀或寫)已經準備就緒的那些通道。換句話說,如果你對”讀就緒“的通道感興趣,select()方法會傳回讀事件已經就緒的那些通道。
- select() —— 阻塞到至少有一個通道在你注冊的事件上就緒了
- select(long timeout) —— 和select()一樣,除了最長會阻塞timeout毫秒
- selectNow() —— 不會阻塞,不管什麼通道就緒都立刻傳回;此方法執行非阻塞的選擇操作,如果自從上一次選擇操作後,沒有通道變成可選擇的,則此方法直接傳回0
- select()方法傳回的Int值表示多少通道就緒。
一旦調用了select()方法,并且傳回值表明有一個或更多個通道就緒了,然後可以通過調用selector的selectorKeys()方法,通路”已選擇鍵集“中的就緒通道,
Set selectedKeys = selector.selectedKeys();
可以周遊這個已選擇的集合來通路就緒的通道:
- Set selectedKeys = selector.selectedKeys();
- Iterator 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 eatablished 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();
- }
這個循環周遊已選擇集中的每個鍵,并檢測各個鍵所對象的通道的就緒事件。
注意每次疊代末尾的remove()調用,Selector不會自己從已選擇集中移除SelectioKey執行個體,必須在處理完通道時自己移除。
Selector的wakeUp()方法
某個線程調用select()方法後阻塞了,即使沒有通道已經就緒,也有辦法讓其從select()方法傳回。隻要讓其他線程在第一個線程調用select()方法的那個對象上調用Selector.wakeup()方法即可。阻塞在select()方法上的線程會立馬傳回。
一個示例
打開一個Selector,注冊一個通道注冊到這個Selector上,然後持續監控這個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 selectedKeys = selector.selectedKeys();
- Iterator 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();
- }
- }