4.3 使用選擇器既然我們已經(jīng)很好地掌握了各種不同類以及它們之間的關(guān)聯(lián),那么現(xiàn)在讓我們進(jìn)一步了解Selector 類,也就是就緒選擇的核心。這里是Selector 類的可用的 API。在 4.1.2 小節(jié)中,我們已經(jīng)看到如何創(chuàng)建新的選擇器,那么那些方法還剩下: 4.3.1 選擇過程 在詳細(xì)了解API之前,您需要知道一點(diǎn)和Selector 內(nèi)部工作原理相關(guān)的知識(shí)。就像上面探討的那樣,選擇器維護(hù)著注冊過的通道的集合,并且這些注冊關(guān)系中的任意一個(gè)都是封裝在SelectionKey 對(duì)象中的。每一個(gè)Selector 對(duì)象維護(hù)三個(gè)鍵的集合: - public abstract class Selector {
- // 這里僅列出部分API
- public abstract Set keys();
- public abstract Set selectedKeys();
- public abstract int select() throws IOException;
- public abstract int select (long timeout) throws IOException;
- public abstract int selectNow() throws IOException;
- public abstract void wakeup();
- }
- 已注冊的鍵的集合(Registered key set)
- 與選擇器關(guān)聯(lián)的已經(jīng)注冊的鍵的集合。并不是所有注冊過的鍵都仍然有效。這個(gè)集合通過
keys() 方法返回,并且可能是空的。這個(gè)已注冊的鍵的集合不是可以直接修改的;試圖這么做的話將引發(fā)java.lang.UnsupportedOperationException 。 - 已選擇的鍵的集合(Selected key set)
- 已注冊的鍵的集合的子集。這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器(在前一個(gè)選擇操作中)判斷為已經(jīng)準(zhǔn)備好的,并且包含于鍵的interest集合中的操作。這個(gè)集合通過
selectedKeys() 方法返回(并有可能是空的)。不要將已選擇的鍵的集合與ready集合弄混了。這是一個(gè)鍵的集合,每個(gè)鍵都關(guān)聯(lián)一個(gè)已經(jīng)準(zhǔn)備好至少一種操作的通道。每個(gè)鍵都有一個(gè)內(nèi)嵌的ready集合,指示了所關(guān)聯(lián)的通道已經(jīng)準(zhǔn)備好的操作。 鍵可以直接從這個(gè)集合中移除,但不能添加。試圖向已選擇的鍵的集合中添加元素將拋出java.lang.UnsupportedOperationException 。 - 已取消的鍵的集合(Cancelled key set)
- 已注冊的鍵的集合的子集,這個(gè)集合包含了
cancel() 方法被調(diào)用過的鍵(這個(gè)鍵已經(jīng)被無效化),但它們還沒有被注銷。這個(gè)集合是選擇器對(duì)象的私有成員,因而無法直接訪問。
在一個(gè)剛初始化的Selector 對(duì)象中,這三個(gè)集合都是空的。 Selector 類的核心是選擇過程。這個(gè)名詞您已經(jīng)在之前看過多次了——現(xiàn)在應(yīng)該解釋一下了?;旧蟻碚f,選擇器是對(duì)select() 、poll() 等本地調(diào)用(native call)或者類似的操作系統(tǒng)特定的系統(tǒng)調(diào)用的一個(gè)包裝。但是Selector 所作的不僅僅是簡單地向本地代碼傳送參數(shù)。它對(duì)每個(gè)選擇操作應(yīng)用了特定的過程。對(duì)這個(gè)過程的理解是合理地管理鍵和它們所表示的狀態(tài)信息的基礎(chǔ)。
選擇操作是當(dāng)三種形式的select() 中的任意一種被調(diào)用時(shí),由選擇器執(zhí)行的。不管是哪一種形式的調(diào)用,下面步驟將被執(zhí)行:
- 已取消的鍵的集合將會(huì)被檢查。如果它是非空的,每個(gè)已取消的鍵的集合中的鍵將從另外兩個(gè)集合中移除,并且相關(guān)的通道將被注銷。這個(gè)步驟結(jié)束后,已取消的鍵的集合將是空的。
- 已注冊的鍵的集合中的鍵的interest集合將被檢查。在這個(gè)步驟中的檢查執(zhí)行過后,對(duì)interest集合的改動(dòng)不會(huì)影響剩余的檢查過程。
一旦就緒條件被定下來,底層操作系統(tǒng)將會(huì)進(jìn)行查詢,以確定每個(gè)通道所關(guān)心的操作的真實(shí)就緒狀態(tài)。依賴于特定的select() 方法調(diào)用,如果沒有通道已經(jīng)準(zhǔn)備好,線程可能會(huì)在這時(shí)阻塞,通常會(huì)有一個(gè)超時(shí)值。 直到系統(tǒng)調(diào)用完成為止,這個(gè)過程可能會(huì)使得調(diào)用線程睡眠一段時(shí)間,然后當(dāng)前每個(gè)通道的就緒狀態(tài)將確定下來。對(duì)于那些還沒準(zhǔn)備好的通道將不會(huì)執(zhí)行任何的操作。對(duì)于那些操作系統(tǒng)指示至少已經(jīng)準(zhǔn)備好interest集合中的一種操作的通道,將執(zhí)行以下兩種操作中的一種: a.如果通道的鍵還沒有處于已選擇的鍵的集合中,那么鍵的ready集合將被清空,然后表示操作系統(tǒng)發(fā)現(xiàn)的當(dāng)前通道已經(jīng)準(zhǔn)備好的操作的比特掩碼將被設(shè)置。 b.否則,也就是鍵在已選擇的鍵的集合中。鍵的ready集合將被表示操作系統(tǒng)發(fā)現(xiàn)的當(dāng)前已經(jīng)準(zhǔn)備好的操作的比特掩碼更新。所有之前的已經(jīng)不再是就緒狀態(tài)的操作不會(huì)被清除。事實(shí)上,所有的比特位都不會(huì)被清理。由操作系統(tǒng)決定的ready集合是與之前的ready集合按位分離的,一旦鍵被放置于選擇器的已選擇的鍵的集合中,它的ready集合將是累積的。比特位只會(huì)被設(shè)置,不會(huì)被清理。 - 步驟2可能會(huì)花費(fèi)很長時(shí)間,特別是所激發(fā)的線程處于休眠狀態(tài)時(shí)。與該選擇器相關(guān)的鍵可能會(huì)同時(shí)被取消。當(dāng)步驟2結(jié)束時(shí),步驟1將重新執(zhí)行,以完成任意一個(gè)在選擇進(jìn)行的過程中,鍵已經(jīng)被取消的通道的注銷。
- select操作返回的值是ready集合在步驟2中被修改的鍵的數(shù)量,而不是已選擇的鍵的集合中的通道的總數(shù)。返回值不是已準(zhǔn)備好的通道的總數(shù),而是從上一個(gè)
select() 調(diào)用之后進(jìn)入就緒狀態(tài)的通道的數(shù)量。之前的調(diào)用中就緒的,并且在本次調(diào)用中仍然就緒的通道不會(huì)被計(jì)入,而那些在前一次調(diào)用中已經(jīng)就緒但已經(jīng)不再處于就緒狀態(tài)的通道也不會(huì)被計(jì)入。這些通道可能仍然在已選擇的鍵的集合中,但不會(huì)被計(jì)入返回值中。返回值可能是0。使用內(nèi)部的已取消的鍵的集合來延遲注銷,是一種防止線程在取消鍵時(shí)阻塞,并防止與正在進(jìn)行的選擇操作沖突的優(yōu)化。注銷通道是一個(gè)潛在的代價(jià)很高的操作,這可能需要重新分配資源(請(qǐng)記住,鍵是與通道相關(guān)的,并且可能與它們相關(guān)的通道對(duì)象之間有復(fù)雜的交互)。清理已取消的鍵,并在選擇操作之前和之后立即注銷通道,可以消除它們可能正好在選擇的過程中執(zhí)行的潛在棘手問題。這是另一個(gè)兼顧健壯性的折中方案。 Selector 類的select() 方法有以下三種不同的形式:
這三種select的形式,僅僅在它們在所注冊的通道當(dāng)前都沒有就緒時(shí),是否阻塞的方面有所不同。最簡單的沒有參數(shù)的形式可以用如下方式調(diào)用:
public abstract int select() throws IOException; 這種調(diào)用在沒有通道就緒時(shí)將無限阻塞。一旦至少有一個(gè)已注冊的通道就緒,選擇器的選擇鍵就會(huì)被更新,并且每個(gè)就緒的通道的ready集合也將被更新。返回值將會(huì)是已經(jīng)確定就緒的通道的數(shù)目。正常情況下,這些方法將返回一個(gè)非零的值,因?yàn)橹钡揭粋€(gè)通道就緒前它都會(huì)阻塞。但是它也可以返回非0值,如果選擇器的wakeup() 方法被其他線程調(diào)用。 有時(shí)您會(huì)想要限制線程等待通道就緒的時(shí)間。這種情況下,可以使用一個(gè)接受一個(gè)超時(shí)參數(shù)的select() 方法的重載形式: public abstract int select(long timeout) throws IOException; 這種調(diào)用與之前的例子完全相同,除了如果在您提供的超時(shí)時(shí)間(以毫秒計(jì)算)內(nèi)沒有通道就緒時(shí),它將返回0。如果一個(gè)或者多個(gè)通道在時(shí)間限制終止前就緒,鍵的狀態(tài)將會(huì)被更新,并且方法會(huì)在那時(shí)立即返回。將超時(shí)參數(shù)指定為0表示將無限期等待,那么它就在各個(gè)方面都等同于使用無參數(shù)版本的select() 了。 就緒選擇的第三種也是最后一種形式是完全非阻塞的: int n = selector.selectNow(); selectNow() 方法執(zhí)行就緒檢查過程,但不阻塞。如果當(dāng)前沒有通道就緒,它將立即返回0。
Java nio入門教程詳解(三十五)
|