selector是一個檢測一個或者多個channel的組件深纲,能偵測哪個channel裝備好寫或者讀操作仲锄。這種方式下,一個單線程就可以管理多個channel或者多個網絡鏈接湃鹊。
為什么使用selector儒喊?
使用單線程去處理多channel的優(yōu)勢就是你只需要少量的線程去處理多channel。甚至涛舍,你可以用一個線程去處理所有的channel澄惊。對操作系統(tǒng)來說,線程切換是很費資源的富雅,并且在操作系統(tǒng)中掸驱,每個線程都會占據資源(內存),因此線程越少没佑,越好毕贼。
但請記住,現(xiàn)代的操作系統(tǒng)和cpu在多任務處理上正變的越來越好蛤奢,多線程花費的時間越來越少鬼癣。實際上陶贼,如果一個cpu是多核的,你不做多任務處理的話待秃,可能正是一種資源浪費拜秧。總之章郁,這塊屬于不同區(qū)域的討論枉氮。僅對此處而言,一個selector可以用一個縣城來處理多個Channel暖庄。
Creating a Selector
創(chuàng)建一個selector:
Selector selector = Selector.open();
注冊channel到Selector上:
使用selector的前提就是channel必須是在非阻塞的模式下聊替。這就意味著你不能將FileChannel注冊到selector上,前面文章的例子都是FileChannel培廓。但是Socket 相關的channel就能很好的使用selector惹悄。
注意register方法的第二個參數(shù),
這是一個有趣的設置肩钠,意味著這個channel通過selector對什么事件感興趣泣港,有以下四個事件可以監(jiān)聽到:
? ? ?Connect ? ? Accept ? ? Read ? ?Write
一個Channel啟動一個事件代表它為這個事件轉備好了。因此蔬将,channel鏈接上服務器就是一個連接準備爷速,一個socket Channel接受一個連接代表接受事件,一個channel有數(shù)據準備去讀代表一個準備讀事件霞怀。
如果一個selector對多個事件感興趣,則
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
傳入到register方法中莉给。
SelectionKey
register()方法的返回對象就是SelectionKey毙石。SelectionKey含有幾個有意思的特性;
Interest Set
通過以下方法可以獲得register()方法的時候的事件類型
你可以使用&連接符連接SelectionKey的常量來找出Selector對哪些事件感興趣
Ready Set
ready set 是channel的一些列可操作的狀態(tài)集合颓遏,你可以獲取這個狀態(tài)集合通過一個selection徐矩。
int readySet = selectionKey.readyOps();
下面的方法跟上面的效果一樣
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector
你可以通過SelectionKey來訪問Channel和Selector對象
Channel? channel? = selectionKey.channel();
Selector selector = selectionKey.selector();
Attaching Objects
你可以在selectionKey附加一個Object對象,來標識channel對象以便找出你要的channel對象叁幢,或者附加一些其他的信息滤灯。舉個栗子,你可以將buffer對象附加在上面
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
你可以在注冊Selector的時候把對象附加上去
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channels via a Selector
一旦你注冊了一個或者多個channel到一個Selector上面曼玩,你就可以調用以下三個select方法鳞骤。這些方法會返回準備好的事件的對象,這些事件(connect,accept,read 或者write)都是你當初注冊Selector時候傳遞的黍判。換句話說如果你注冊的時候是傳遞的讀事件豫尽,那么當你調用select的時候,獲得當前準備去讀的channels顷帖。
int select()
int select(long timeout)
int selectNow()
select()會阻塞美旧,直到至少一個已經轉備好事件的channel渤滞。
select(long timeout)?跟上面方法一樣,除了他會在timeout毫秒內會阻塞.
selectNow()不會阻塞榴嗅,會立即返回
selectedKeys()
當你調用select()方法的時候妄呕,返回的值代表有多少個channel準備好了,你可以通過selected key set去訪問準備好的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.
當你調用channel的register的方法的時候嗽测,會返回一個SelectionKey趴腋。這個代表channel和selector的關系
remove方法是必須要調用的,當你處理好channel论咏。通過SelectionKey.channel()會獲得channel對象优炬,你可以類型轉換你要的Channel對象,比如ServerSocketChannel等
wakeUp()
一個線程調用select()就會阻塞厅贪,但是也可以脫離select()方法回到非阻塞狀態(tài)蠢护,即使沒有channel轉備好。這種方式就是讓其他線程調用那個selector的wakeup()方法养涮,然后select()方法會立即返回葵硕。
如果另外一個線程調用這個selector的wakeup方法,并且原來調用selector的select()方法的線程并沒有阻塞贯吓,那么下一個調用selector的select()方法會立即返回懈凹。
close()
與注冊相反,這個方法會使所有的selectKey無效
完整的栗子