4.2 使用選擇鍵讓我們看看SelectionKey 類(lèi)的API: - package java.nio.channels;
- public abstract class SelectionKey {
- public static final int OP_READ
- public static final int OP_WRITE
- public static final int OP_CONNECT
- public static final int OP_ACCEPT
- public abstract SelectableChannel channel();
- public abstract Selector selector();
- public abstract void cancel();
- public abstract boolean isValid();
- public abstract int interestOps();
- public abstract void interestOps(int ops);
- public abstract int readyOps();
- public final boolean isReadable()
- public final boolean isWritable()
- public final boolean isConnectable()
- public final boolean isAcceptable()
- public final Object attach(Object ob)
- public final Object attachment()
- }
就像之前提到的那樣,一個(gè)鍵表示了一個(gè)特定的通道對(duì)象和一個(gè)特定的選擇器對(duì)象之間的注冊(cè)關(guān)系 。 您可以看到前兩個(gè)方法中反映了這種關(guān)系 。 channel() 方法返回與該鍵相關(guān)的SelectableChannel 對(duì)象,而selector() 則返回相關(guān)的Selector 對(duì)象。這沒(méi)有什么令人驚奇的。 鍵對(duì)象表示了一種特定的注冊(cè)關(guān)系。當(dāng)應(yīng)該終結(jié)這種關(guān)系的時(shí)候,可以調(diào)用SelectionKey 對(duì)象的cancel() 方法??梢酝ㄟ^(guò)調(diào)用isValid() 方法來(lái)檢查它是否仍然表示一種有效的關(guān)系。當(dāng)鍵被取消時(shí),它將被放在相關(guān)的選擇器的已取消的鍵的集合里。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效(參見(jiàn) 4.3 節(jié))。當(dāng)再次調(diào)用select() 方法時(shí)(或者一個(gè)正在進(jìn)行的select() 調(diào)用結(jié)束時(shí)),已取消的鍵的集合中的被取消的鍵將被清理掉,并且相應(yīng)的注銷(xiāo)也將完成。通道會(huì)被注銷(xiāo),而新的SelectionKey 將被返回。 當(dāng)通道關(guān)閉時(shí),所有相關(guān)的鍵會(huì)自動(dòng)取消(記住,一個(gè)通道可以被注冊(cè)到多個(gè)選擇器上)。當(dāng)選擇器關(guān)閉時(shí),所有被注冊(cè)到該選擇器的通道都將被注銷(xiāo),并且相關(guān)的鍵將立即被無(wú)效化(取消)。一旦鍵被無(wú)效化,調(diào)用它的與選擇相關(guān)的方法就將拋出CancelledKeyException 。 一個(gè)SelectionKey 對(duì)象包含兩個(gè)以整數(shù)形式進(jìn)行編碼的比特掩碼:一個(gè)用于指示那些通道/選擇器組合體所關(guān)心的操作(instrest集合),另一個(gè)表示通道準(zhǔn)備好要執(zhí)行的操作(ready集合)。當(dāng)前的interest集合可以通過(guò)調(diào)用鍵對(duì)象的interestOps() 方法來(lái)獲取。最初,這應(yīng)該是通道被注冊(cè)時(shí)傳進(jìn)來(lái)的值。這個(gè)interset集合永遠(yuǎn)不會(huì)被選擇器改變,但您可以通過(guò)調(diào)用interestOps() 方法并傳入一個(gè)新的比特掩碼參數(shù)來(lái)改變它。interest集合也可以通過(guò)將通道注冊(cè)到選擇器上來(lái)改變(實(shí)際上使用一種迂回的方式調(diào)用interestOps() ),就像 4.1.2 小節(jié)中描的那樣。當(dāng)相關(guān)的Selector 上的select() 操作正在進(jìn)行時(shí)改變鍵的interest集合,不會(huì)影響那個(gè)正在進(jìn)行的選擇操作。所有更改將會(huì)在select() 的下一個(gè)調(diào)用中體現(xiàn)出來(lái)。 可以通過(guò)調(diào)用鍵的readyOps() 方法來(lái)獲取相關(guān)的通道的已經(jīng)就緒的操作。ready集合是interest集合的子集,并且表示了interest 集合中從上次調(diào)用 select() 以來(lái)已經(jīng)就緒的那些操作。例如,下面的代碼測(cè)試了與鍵關(guān)聯(lián)的通道是否就緒。如果就緒,就將數(shù)據(jù)讀取出來(lái),寫(xiě)入一個(gè)緩沖區(qū),并將它送到一個(gè)consumer(消費(fèi)者)方法中。 - if ((key.readyOps() & SelectionKey.OP_READ) != 0){
- myBuffer.clear();
- key.channel().read(myBuffer);
- doSomethingWithBuffer(myBuffer.flip());
- }
就像之前提到過(guò)的那樣,有四個(gè)通道操作可以被用于測(cè)試就緒狀態(tài)。您可以像上面的代碼那樣,通過(guò)測(cè)試比特掩碼來(lái)檢查這些狀態(tài),但SelectionKey類(lèi)定義了四個(gè)便于使用的布爾方法來(lái)為您測(cè)試這些比特值:isReadable() ,isWritable() ,isConnectable() , 和 isAcceptable() 。每一個(gè)方法都與使用特定掩碼來(lái)測(cè)試readyOps() 方法的結(jié)果的效果相同。例如: if (key.isWritable()) 等價(jià)于: if ((key.readyOps() & SelectionKey.OP_WRITE) != 0) 這四個(gè)方法在任意一個(gè)SelectionKey 對(duì)象上都能安全地調(diào)用。不能在一個(gè)通道上注冊(cè)一個(gè)它不支持的操作,這種操作也永遠(yuǎn)不會(huì)出現(xiàn)在ready集合中。調(diào)用一個(gè)不支持的操作將總是返回false ,因?yàn)檫@種操作在該通道上永遠(yuǎn)不會(huì)準(zhǔn)備好。 需要注意的是,通過(guò)相關(guān)的選擇鍵的readyOps() 方法返回的就緒狀態(tài)指示只是一個(gè)提示,不是保證。底層的通道在任何時(shí)候都會(huì)不斷改變。其他線(xiàn)程可能在通道上執(zhí)行操作并影響它的就緒狀態(tài)。同時(shí),操作系統(tǒng)的特點(diǎn)也總是需要考慮的。 SelectionKey 對(duì)象包含的ready集合與最近一次選擇器對(duì)所注冊(cè)的通道所作的檢查相同。而每個(gè)單獨(dú)的通道的就緒狀態(tài)會(huì)同時(shí)改變。
您可能會(huì)從SelectionKey 的API中注意到盡管有獲取ready集合的方法,但沒(méi)有重新設(shè)置那個(gè)集合的成員方法。事實(shí)上,您不能直接改變鍵的ready集合。在下一節(jié)里,也就是描述選擇過(guò)程時(shí),我們將會(huì)看到選擇器和鍵是如何進(jìn)行交互,以提供實(shí)時(shí)更新的就緒指示的。 讓我們?cè)囼?yàn)一下SelectionKey 的API中剩下的兩個(gè)方法: - public abstract class SelectionKey {
- // 這里僅列出部分API
- public final Object attach (Object ob)
- public final Object attachment()
- }
這兩個(gè)方法允許您在鍵上放置一個(gè)「附件」,并在后面獲取它。這是一種允許您將任意對(duì)象與鍵關(guān)聯(lián)的便捷的方法。這個(gè)對(duì)象可以引用任何對(duì)您而言有意義的對(duì)象,例如業(yè)務(wù)對(duì)象、會(huì)話(huà)句柄、其他通道等等。這將允許您遍歷與選擇器相關(guān)的鍵,使用附加在上面的對(duì)象句柄作為引用來(lái)獲取相關(guān)的上下文。 attach() 方法將在鍵對(duì)象中保存所提供的對(duì)象的引用。SelectionKey 類(lèi)除了保存它之外,不會(huì)將它用于任何其他用途。任何一個(gè)之前保存在鍵中的附件引用都會(huì)被替換??梢允褂?code>null值來(lái)清除附件??梢酝ㄟ^(guò)調(diào)用attachment() 方法來(lái)獲取與鍵關(guān)聯(lián)的附件句柄。如果沒(méi)有附件,或者顯式地通過(guò)null 方法進(jìn)行過(guò)設(shè)置,這個(gè)方法將返回null 。
如果選擇鍵的存續(xù)時(shí)間很長(zhǎng),但您附加的對(duì)象不應(yīng)該存在那么長(zhǎng)時(shí)間,請(qǐng)記得在完成后清理附件。否則,您附加的對(duì)象將不能被垃圾回收,您將會(huì)面臨內(nèi)存泄漏問(wèn)題。 SelectableChannel 類(lèi)的一個(gè)register() 方法的重載版本接受一個(gè)Object類(lèi)型的參數(shù)。這是一個(gè)方便您在注冊(cè)時(shí)附加一個(gè)對(duì)象到新生成的鍵上的方法。以下代碼:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, myObject); 等價(jià)于: - SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
- key.attach (myObject);
關(guān)于SelectionKey 的最后一件需要注意的事情是并發(fā)性??傮w上說(shuō),SelectionKey 對(duì)象是線(xiàn)程安全的,但知道修改interest集合的操作是通過(guò)Selector對(duì)象進(jìn)行同步的是很重要的。這可能會(huì)導(dǎo)致interestOps() 方法的調(diào)用會(huì)阻塞不確定長(zhǎng)的一段時(shí)間。選擇器所使用的鎖策略(例如是否在整個(gè)選擇過(guò)程中保持這些鎖)是依賴(lài)于具體實(shí)現(xiàn)的。幸好,這種多元處理能力被特別地設(shè)計(jì)為可以使用單線(xiàn)程來(lái)管理多個(gè)通道。被多個(gè)線(xiàn)程使用的選擇器也只會(huì)在系統(tǒng)特別復(fù)雜時(shí)產(chǎn)生問(wèn)題。坦白地說(shuō),如果您在多線(xiàn)程中共享選擇器時(shí)遇到了同步的問(wèn)題,也許您需要重新思考一下您的設(shè)計(jì)。 我們已經(jīng)探討了SelectionKey 的 API,但我們還沒(méi)有談完選擇鍵的一切——遠(yuǎn)遠(yuǎn)沒(méi)有。讓我們進(jìn)一步了解如何使用選擇器管理鍵吧。 Java nio入門(mén)教程詳解(三十四)
0
0
我們認(rèn)為:用戶(hù)的主要目的,是為了獲取有用的信息,而不是來(lái)點(diǎn)擊廣告的。因此本站將竭力做好內(nèi)容,并將廣告和內(nèi)容進(jìn)行分離,確保所有廣告不會(huì)影響到用戶(hù)的正常閱讀體驗(yàn)。用戶(hù)僅憑個(gè)人意愿和興趣愛(ài)好點(diǎn)擊廣告。
我們堅(jiān)信:只有給用戶(hù)帶來(lái)價(jià)值,用戶(hù)才會(huì)給我們以回報(bào)。
|