Java NIO(七)Selector

選擇器是Java NIO組件踊东,它可以檢查一個或多個NIO通道产徊,并確定哪些通道準備好 閱讀或?qū)懽鳌?這樣一個單一的線程可以管理多個通道,從而可以管理多個網(wǎng)絡連接井赌。

為什么要用Selector

使用單個線程來處理多個通道的優(yōu)點是您需要較少的線程來處理通道谤逼。 實際上,你可以只用一個線程來處理你所有的頻道仇穗。 線程之間的切換對于操作系統(tǒng)而言是昂貴的流部,并且每個線程也占用操作系統(tǒng)中的一些資源(存儲器)。 因此纹坐,你使用的線程越少越好枝冀。

但請記住,現(xiàn)代操作系統(tǒng)和CPU在多任務處理方面越來越好耘子,所以多線程的開銷隨著時間的推移而變小果漾。 事實上,如果一個CPU有多個核心谷誓,那么你可能會因為沒有多任務而浪費CPU資源绒障。 無論如何,這個設計討論屬于不同的文本捍歪。 在這里說一下就足夠了户辱,你可以使用一個Selector來處理單個線程的多個通道。

下面是一個使用Selector處理3個Channel的線程的例子:


image.png

創(chuàng)建一個Selector

您可以通過調(diào)用Selector.open()方法創(chuàng)建一個Selector糙臼,如下所示:

Selector selector = Selector.open();

用Selector注冊channel

為了將頻道與選擇器一起使用庐镐,您必須使用選擇器注冊頻道。 這是使用SelectableChannel.register()方法完成的弓摘,如下所示:

channel.configureBlocking(false);//非阻塞

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

通道必須處于非阻塞模式才能與選擇器一起使用焚鹊。 這意味著你不能使用FileChannel和Selector,因為FileChannel不能切換到非阻塞模式韧献。 套接字通道將正常工作末患。

注意第二個參數(shù),這是一個按照類型設置的锤窑,意味著你設置的監(jiān)聽Channel的事件類型璧针,經(jīng)過選擇器,有四個不通的事件:

  1. Connect
  2. Accept
  3. Read
  4. Write
    “發(fā)生事件”的頻道也被認為是“準備就緒”的事件渊啰。 因此探橱,成功連接到另一臺服務器的通道是“連接Connect就緒”申屹。 接受傳入連接的服務器套接字通道是“接受Accept”就緒。 準備好讀取數(shù)據(jù)Read的通道已經(jīng)準備就緒隧膏。 一個準備好寫入數(shù)據(jù)的通道Write已經(jīng)準備好了哗讥。
    這四個事件由四個SelectionKey常量表示:
  5. SelectionKey.OP_CONNECT
  6. SelectionKey.OP_ACCEPT
  7. SelectionKey.OP_READ
  8. SelectionKey.OP_WRITE
    如果你需要設置多個事件,或者將常量放在一起胞枕,就像這樣:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;    

SelectionKey

正如你在上一節(jié)看到的杆煞,當你用一個選擇器注冊一個Channel時,register()方法返回一個SelectionKey對象腐泻。 這個SelectionKey對象包含一些有趣的屬性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)
    下面我來介紹下這幾個屬性
Interest Set

The 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常數(shù)和興趣組發(fā)現(xiàn)如果一個特定的事件是為了集合派桩。

Ready Set

Ready Set是通道準備好的一組操作构诚。 您將主要在選擇后訪問Ready Set。 選擇在后面的章節(jié)中解釋铆惑。 你可以這樣訪問readyset:

int readySet = selectionKey.readyOps();

您可以按照與興趣集相同的方式測試頻道準備好的事件/操作范嘱。 但是,您也可以使用這四種方法鸭津,而這些方法都是重新構造一個布爾值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector

從SelectionKey訪問通道+選擇器是很簡潔的彤侍。 這是如何完成的:

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();    
Attaching Objects

您可以將對象附加到SelectionKey,這是識別給定通道channel或?qū)⒏嘈畔⒏郊拥酵ǖ纁hannel的便捷方式逆趋。 例如,您可以將您正在使用的緩沖區(qū)與通道或包含更多聚合數(shù)據(jù)的對象連接起來晒奕。 這里是你如何附加對象:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

在register()方法中闻书,您也可以在向Selector注冊Channel時附加一個對象。 這是如何看起來如此:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
通過選擇器來選擇通道

一旦你用一個Selector注冊了一個或多個通道脑慧,你可以調(diào)用其中一個select()方法魄眉。 這些方法返回您感興趣的事件(連接,接受闷袒,讀取或?qū)懭耄┑摹皽蕚浜谩钡耐ǖ馈?換句話說坑律,如果您對準備閱讀的頻道感興趣,您將收到準備從select()方法讀取的通道囊骤。
這里是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow()
    int select()晃择,直到至少有一個通道準備好注冊的事件。
    int select(long timeout):和select()一樣的也物,除了它的最大超時毫秒(參數(shù))宫屠。
    int selectNow():它返回立即用什么渠道都準備好了。
    select()方法返回的int告訴準備好多少個通道滑蚯。 也就是說浪蹂,自上次調(diào)用select()以來已經(jīng)準備了多少個通道抵栈。 如果你調(diào)用select(),并返回1坤次,因為一個通道已經(jīng)準備好了古劲,而且你再次調(diào)用select(),并且另外一個通道已經(jīng)準備就緒缰猴,它將再次返回1产艾。 如果您已經(jīng)準備好的第一個頻道沒有做任何事情,那么您現(xiàn)在有2個就緒頻道洛波,但在每個select()調(diào)用之間只有一個頻道已準備就緒胰舆。
selectedKeys()

一旦你調(diào)用了一個select()方法,并且它的返回值已經(jīng)表明一個或者多個通道已經(jīng)準備就緒蹬挤,你可以通過調(diào)用選擇器selectedKeys()方法來通過“selected key set”訪問ready channels缚窿。 這是如何看起來如此:

Set<SelectionKey> selectedKeys = selector.selectedKeys();    

當您使用Selector注冊頻道時,Channel.register()方法將返回SelectionKey對象焰扳。 這個鍵表示通道注冊到該選擇器倦零。 這些鍵可以通過selectedKeySet()方法訪問。 從SelectionKey吨悍。
您可以迭代此選定的selected key集以訪問就緒通道扫茅。 這是如何看起來如此:

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)迭代所選鍵集中的鍵。 對于每個SelectionKey 育瓜,它測試SelectionKey 以確定SelectionKey 引用的通道已準備好葫隙。

注意每次迭代結束時的keyIterator.remove()調(diào)用。 選擇器不會從選定的SelectionKey 集本身中刪除SelectionKey實例躏仇。 當您完成頻道處理后恋脚,您必須執(zhí)行此操作。 通道下一次變?yōu)椤熬途w”時焰手,選擇器將再次將其添加到所選的selected key 糟描。

SelectionKey.channel()方法返回的頻道應該被轉換為你需要使用的頻道,例如ServerSocketChannel或SocketChannel等书妻。

wakeUp()

調(diào)用被阻塞的select()方法的線程可以離開select()方法船响,即使沒有通道尚未準備好。 這是通過讓不同的線程調(diào)用Selector上的Selector.wakeup()方法來完成的躲履,第一個線程調(diào)用了select()见间。 在select()中等待的線程將立即返回。

如果一個不同的線程調(diào)用wakeup()并且select()內(nèi)部當前沒有線程被阻塞崇呵,那么調(diào)用select()的下一個線程將立即“喚醒”缤剧。

close()

當你完成Selector時,你調(diào)用close()方法域慷。 這將關閉選擇器荒辕,并使在此選擇器中注冊的所有SelectionKey實例失效汗销。 渠道本身并沒有關閉。

完整的Selector的例子

這是一個完整的例子抵窒,它打開一個選擇器弛针,注冊一個通道(通道實例被省略),并持續(xù)監(jiān)控選擇器是否準備好四個事件(接受李皇,連接削茁,讀取,寫入)掉房。

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();
  }
}
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茧跋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卓囚,更是在濱河造成了極大的恐慌瘾杭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪亿,死亡現(xiàn)場離奇詭異粥烁,居然都是意外死亡,警方通過查閱死者的電腦和手機蝇棉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門讨阻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篡殷,你說我怎么就攤上這事钝吮。” “怎么了板辽?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵搀绣,是天一觀的道長。 經(jīng)常有香客問我戳气,道長,這世上最難降的妖魔是什么巧鸭? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任瓶您,我火速辦了婚禮,結果婚禮上纲仍,老公的妹妹穿的比我還像新娘呀袱。我一直安慰自己,他們只是感情好郑叠,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布夜赵。 她就那樣靜靜地躺著,像睡著了一般乡革。 火紅的嫁衣襯著肌膚如雪寇僧。 梳的紋絲不亂的頭發(fā)上摊腋,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機與錄音嘁傀,去河邊找鬼兴蒸。 笑死,一個胖子當著我的面吹牛细办,可吹牛的內(nèi)容都是我干的橙凳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼笑撞,長吁一口氣:“原來是場噩夢啊……” “哼岛啸!你這毒婦竟也來了?” 一聲冷哼從身側響起茴肥,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坚踩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后炉爆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堕虹,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年芬首,在試婚紗的時候發(fā)現(xiàn)自己被綠了赴捞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡郁稍,死狀恐怖赦政,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耀怜,我是刑警寧澤恢着,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站财破,受9級特大地震影響掰派,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜左痢,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一靡羡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俊性,春花似錦略步、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至典徊,卻和暖如春杭煎,著一層夾襖步出監(jiān)牢的瞬間恩够,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工岔帽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玫鸟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓犀勒,卻偏偏與公主長得像屎飘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贾费,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API钦购,可以替代標準的Java I...
    JackChen1024閱讀 7,555評論 1 143
  • 參考:http://ifeve.com/selectors/原文地址 目錄 Java NIO教程 Java NIO...
    步積閱讀 4,490評論 3 10
  • Selector(選擇器)是Java NIO中能夠同時監(jiān)測多個Channel通道,并且還能知道Channel上讀寫...
    zhanglbjames閱讀 3,074評論 0 6
  • 一褂萧、基本概念描述 1.1 I/O簡介 I/O即輸入輸出押桃,是計算機與外界世界的一個借口。IO操作的實際主題是操作系統(tǒng)...
    4ea0af17fd67閱讀 413評論 0 0
  • 這兩天了解了一下關于NIO方面的知識导犹,網(wǎng)上關于這一塊的介紹只是介紹了一下基本用法唱凯,沒有系統(tǒng)的解釋NIO與阻塞、非阻...
    Ruheng閱讀 7,128評論 5 48