selector是一個(gè)檢測(cè)一個(gè)或者多個(gè)channel的組件,能偵測(cè)哪個(gè)channel裝備好寫或者讀操作。這種方式下,一個(gè)單線程就可以管理多個(gè)channel或者多個(gè)網(wǎng)絡(luò)鏈接。
為什么使用selector?
使用單線程去處理多channel的優(yōu)勢(shì)就是你只需要少量的線程去處理多channel。甚至,你可以用一個(gè)線程去處理所有的channel。對(duì)操作系統(tǒng)來說,線程切換是很費(fèi)資源的,并且在操作系統(tǒng)中,每個(gè)線程都會(huì)占據(jù)資源(內(nèi)存),因此線程越少,越好。
但請(qǐng)記住,現(xiàn)代的操作系統(tǒng)和cpu在多任務(wù)處理上正變的越來越好,多線程花費(fèi)的時(shí)間越來越少。實(shí)際上,如果一個(gè)cpu是多核的,你不做多任務(wù)處理的話,可能正是一種資源浪費(fèi)。總之,這塊屬于不同區(qū)域的討論。僅對(duì)此處而言,一個(gè)selector可以用一個(gè)縣城來處理多個(gè)Channel。
Creating a Selector
創(chuàng)建一個(gè)selector:
Selector selector = Selector.open();
注冊(cè)channel到Selector上:
使用selector的前提就是channel必須是在非阻塞的模式下。這就意味著你不能將FileChannel注冊(cè)到selector上,前面文章的例子都是FileChannel。但是Socket 相關(guān)的channel就能很好的使用selector。
注意register方法的第二個(gè)參數(shù),
這是一個(gè)有趣的設(shè)置,意味著這個(gè)channel通過selector對(duì)什么事件感興趣,有以下四個(gè)事件可以監(jiān)聽到:
? ? ?Connect ? ? Accept ? ? Read ? ?Write
一個(gè)Channel啟動(dòng)一個(gè)事件代表它為這個(gè)事件轉(zhuǎn)備好了。因此,channel鏈接上服務(wù)器就是一個(gè)連接準(zhǔn)備,一個(gè)socket Channel接受一個(gè)連接代表接受事件,一個(gè)channel有數(shù)據(jù)準(zhǔn)備去讀代表一個(gè)準(zhǔn)備讀事件。
如果一個(gè)selector對(duì)多個(gè)事件感興趣,則
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
傳入到register方法中。
SelectionKey
register()方法的返回對(duì)象就是SelectionKey。SelectionKey含有幾個(gè)有意思的特性;
Interest Set
通過以下方法可以獲得register()方法的時(shí)候的事件類型
你可以使用&連接符連接SelectionKey的常量來找出Selector對(duì)哪些事件感興趣
Ready Set
ready set 是channel的一些列可操作的狀態(tài)集合,你可以獲取這個(gè)狀態(tài)集合通過一個(gè)selection。
int readySet = selectionKey.readyOps();
下面的方法跟上面的效果一樣
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector
你可以通過SelectionKey來訪問Channel和Selector對(duì)象
Channel? channel? = selectionKey.channel();
Selector selector = selectionKey.selector();
Attaching Objects
你可以在selectionKey附加一個(gè)Object對(duì)象,來標(biāo)識(shí)channel對(duì)象以便找出你要的channel對(duì)象,或者附加一些其他的信息。舉個(gè)栗子,你可以將buffer對(duì)象附加在上面
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
你可以在注冊(cè)Selector的時(shí)候把對(duì)象附加上去
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channels via a Selector
一旦你注冊(cè)了一個(gè)或者多個(gè)channel到一個(gè)Selector上面,你就可以調(diào)用以下三個(gè)select方法。這些方法會(huì)返回準(zhǔn)備好的事件的對(duì)象,這些事件(connect,accept,read 或者write)都是你當(dāng)初注冊(cè)Selector時(shí)候傳遞的。換句話說如果你注冊(cè)的時(shí)候是傳遞的讀事件,那么當(dāng)你調(diào)用select的時(shí)候,獲得當(dāng)前準(zhǔn)備去讀的channels。
int select()
int select(long timeout)
int selectNow()
select()會(huì)阻塞,直到至少一個(gè)已經(jīng)轉(zhuǎn)備好事件的channel。
select(long timeout)?跟上面方法一樣,除了他會(huì)在timeout毫秒內(nèi)會(huì)阻塞.
selectNow()不會(huì)阻塞,會(huì)立即返回
selectedKeys()
當(dāng)你調(diào)用select()方法的時(shí)候,返回的值代表有多少個(gè)channel準(zhǔn)備好了,你可以通過selected key set去訪問準(zhǔn)備好的channel
Set selectedKeys = selector.selectedKeys();
When you register a channel with aSelectortheChannel.register()method returns aSelectionKeyobject. This key represents that channels registration with that selector. It is these keys you can access via theselectedKeySet()method. From theSelectionKey.
當(dāng)你調(diào)用channel的register的方法的時(shí)候,會(huì)返回一個(gè)SelectionKey。這個(gè)代表channel和selector的關(guān)系
remove方法是必須要調(diào)用的,當(dāng)你處理好channel。通過SelectionKey.channel()會(huì)獲得channel對(duì)象,你可以類型轉(zhuǎn)換你要的Channel對(duì)象,比如ServerSocketChannel等
wakeUp()
一個(gè)線程調(diào)用select()就會(huì)阻塞,但是也可以脫離select()方法回到非阻塞狀態(tài),即使沒有channel轉(zhuǎn)備好。這種方式就是讓其他線程調(diào)用那個(gè)selector的wakeup()方法,然后select()方法會(huì)立即返回。
如果另外一個(gè)線程調(diào)用這個(gè)selector的wakeup方法,并且原來調(diào)用selector的select()方法的線程并沒有阻塞,那么下一個(gè)調(diào)用selector的select()方法會(huì)立即返回。
close()
與注冊(cè)相反,這個(gè)方法會(huì)使所有的selectKey無效
完整的栗子