Selector是Java NIO的一個組件缔刹,可以檢查一個或者多個NIO channel搂根,并且決定哪個channel已經(jīng)準備好去讀寫了断楷。通過這種方式科平,單個線程可以管理多個channel褥紫,當然也可以管理多個網(wǎng)絡連接。
為什么使用selector ?
使用單個線程處理多個channel的優(yōu)勢是匠抗,你只需要很多的線程去處理大量channel故源。實際上,你可以只使用一個線程來處理所有的channel汞贸。對操作系統(tǒng)來說切換線程是需要大量開銷的绳军,而且每個線程都會占用一些資源(內(nèi)存)。所以你使用的線程越少越好(譯者注:達到目的的情況下矢腻,盡可能使用少的線程)门驾。
不過要記住,當代操作系統(tǒng)和CPU的多任務處理性能已經(jīng)越來越高了多柑,所以多線程的開銷已經(jīng)變得越來越小了奶是。實際上,如果CPU有多核竣灌,你不進行多任務處理則是對CPU性能的浪費聂沙。不管怎么樣,關于這部分內(nèi)容的討論會在其他教程中給出初嘹。但是足夠的是及汉,你可以使用selector來達到使用單線程處理多個channel的目的。
下面的示意圖是表示單個線程來處理三個channel:
創(chuàng)建Selector
通過調(diào)用Selector.open()
來創(chuàng)建Selector:
Selector selector = Selector.open();
使用Selector注冊Channel
為了通過Selector
來使用Channel
屯烦,你需要使用Selector
來注冊Channel
坷随。這可以通過調(diào)用SelectableChannel.register()
方法來實現(xiàn)房铭,如下所示:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Channel要被Selector使用,必須要是非阻塞(non-blocking)模式温眉。這意味著你不能使用Selector來管理FileChannel
缸匪,因為它不能選擇非阻塞模式。但是socket channel是沒有問題的类溢。
注意register()
的第二個參數(shù)凌蔬。這是一個“偏好設置”,意思是你向監(jiān)聽channel的什么事件豌骏,通過Selector.
你可以選擇不同的監(jiān)聽事件龟梦,共有四種不同的監(jiān)聽事件:
- Connect
- Accept
- Read
- Write
所以,一個channel已經(jīng)成功連接到另一個server就是上面的connect事件窃躲。一個server socket正在等待一個連接接入的過程就是上面的accept事件计贰。一個channel有數(shù)據(jù)準備好去被讀取就是read事件。一個channel已經(jīng)準備好讓你往里面寫入數(shù)據(jù)蒂窒,這就是write事件躁倒。
這四個事件用下面四個常量來表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果你想注冊多個事件,可以像下面這樣:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey's
如上面所述洒琢,當你用selector
注冊一個channel秧秉,register()
方法會返回一個SelectionKey
對象。這個對象包含一些有意思的屬性:
- interest set
- ready set
- Channel
- Selector
- 附加對象 (optional)
下面來具體介紹這些屬性衰抑。
Interest Set
Interest Set表示的是你掃描的事件的集合象迎。你可以通過SelectionKey來讀寫這個interest set像下面這樣:
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常量呛踊,去找出某一事件是否在interest set中砾淌。
Ready Set
ready set是channel準備好的操作的集合。主要是在selection后訪問ready set谭网。至于selection汪厨,會在后面給出解釋。你可以像下面這樣訪問ready set:
int readySet = selectionKey.readyOps();
你可以用interest set以同樣的方式來測試channe已經(jīng)做好什么事件的準備了愉择。但是劫乱,你可以利用下面的四個方法來代替,它們都會返回一個布爾值:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel 和 Selector
從SelectionKey來訪問channel和seector是無關緊要的锥涕,下面展示了如何進行此操作:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
附加對象
你可以將一個對象附加到SelectionKey中衷戈,或者給出更多的信息添加至channel,這可以很方便的途徑去識別一個給定的channel层坠。例如殖妇,你可以添加使用的buffer,或者一個包含更多數(shù)據(jù)的對象窿春。下面是相關例子:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
你也可以使用register()
方法拉一,在使用Selector注冊channel時添加這個對象。如下所示:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
通過Selector選擇Channel
當你用Selector
注冊了一個或多個channel旧乞,你可以調(diào)用select()
方法以及其重載的方法蔚润。這些方法會返回你監(jiān)聽的事件(connect, accept, read 和 write)。換句話說尺栖,你可以從這個方法接受到已經(jīng)準備好的事件嫡纠。下面是select()方法以及其相關重載:
int select()
int select(long timeout)
int selectNow()
select()方法會一直阻塞,直到至少一個你注冊的channel已經(jīng)準備好相應事件延赌。
select(long timeout)和select()方法做了同樣的事兒除盏,不過它是阻塞一段時間(多長時間取決于你傳入的事件參數(shù))。
selectNow() 一點也不會阻塞挫以。不管channel準備好沒有者蠕,它立即回返回。
select() 方法返回的int值表示有多少channel已經(jīng)準備好掐松。即:在上一次調(diào)用select()方法之后一共有多少channel已經(jīng)準備好踱侣。如果你調(diào)用select()方法,它返回 1大磺,因為此時只有一個channel準備好抡句,那么你再一次調(diào)用select(),然后期間又有一個channel準備好杠愧,它會再返回1待榔,而不是2。如果你沒有對第一個準備好的channel有任何操作流济,你現(xiàn)在其實已經(jīng)有兩個準備好的channel了锐锣,但是只有一個channel是你兩次調(diào)用之間準備好的channel。
selectedKeys()
調(diào)用select()方法之后袭灯,它會返回一個int值刺下,這表示一個或多個channel已經(jīng)準備好了,你可以通過“selected key set”訪問準備好的channel稽荧,即調(diào)用selectedKeys()
方法:下面是如何操作的:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
當你注冊一個channel時候橘茉,Channel.register()返回一個SelectionKey
對象。這個key表示表示的是這個channel使用了哪個selector來注冊的姨丈〕┳浚可以通過selectedKeySet()
方法來訪問key。
你可以迭代它來訪問準備好的channel:
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();
}
這個循環(huán)迭代selected key集合中的key蟋恬。她再測試每個key以便去確定通過key引用的channel是否已經(jīng)準備好翁潘。
注意,在循環(huán)的最后調(diào)用了keyIterator.remove()
方法歼争。Selector本身并沒有從selected key集合中刪除SelectionKey實例拜马。當你操作完channel后你需要這么做渗勘。下次channel準備好后,selector會再次將其添加至select key集合中俩莽。
方法SelectionKey.channel()
返回的channel可以被強轉成你需要使用的channel旺坠,例如ServerSocketChannel
o或 SocketChannel
。
wakeUp()
調(diào)用select()
方法的線程被阻塞后扮超,你可以讓這個線程離開select()方法取刃,即使channel沒有準備好呢。這需要另一個線程在selector上調(diào)用Selector.wakeup()
方法出刷。
如果一個不同的線程調(diào)用了wakeup()方法璧疗,并且當前已經(jīng)沒有線程在select()阻塞了,那么下一個調(diào)用select()方法的線程會立即喚醒(也就是不阻塞了)馁龟。
close()
當使用完Selector你可以調(diào)用她的close()
方法崩侠。這個操作會關閉selector,并且使所有在此selector上注冊的SelectKey實例失效坷檩。channel本身并沒有關閉啦膜。
完整的Selector例子
下面是一個完成的例子,包括打開Selector淌喻,并使用其注冊channel(channel實例化不考慮)僧家,然后保持selector監(jiān)控四個事件(accept, connect, read, write)。
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();
}
}
想要查看此教程的目錄請點擊:Java NIO教程目錄貼地址