1. 程式人生 > >《Java原始碼解析》之NIO的Selector機制(Part3:Selector.select())

《Java原始碼解析》之NIO的Selector機制(Part3:Selector.select())

Selector.select()函式的分析:

前面已經介紹過了Selector的open函式以及channel的register函式,現在分析最後一個函式:select()函式。

selector.select()在Selector類中此方法是一個抽象的。如下:
public abstract int select() throws IOException;
函式功能:選擇一些I/O操作已經準備好的channel。每個channel對應著一個key。這個方法是一個阻塞的選擇操作。當至少有一個通道被選擇時才返回。當這個方法被執行時,當前執行緒是允許被中斷的。

除了這個方法之外,還有兩個過載方法:
1. public abstract int select(long timeout)throws IOException;
2. public abstract int selectNow() throws IOException;

  1. select(long timeout)
    select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(引數)。
    這個方法並不能提供精確時間的保證,和當執行wait(long timeout)方法時並不能保證會延時timeout道理一樣。

這裡的timeout說明如下:

  • 如果 timeout為正,則select(long timeout)在等待有通道被選擇時至多會阻塞timeout毫秒
  • 如果timeout為零,則永遠阻塞直到有至少一個通道準備就緒。
  • timeout不能為負數
  1. selectNow()
    這個方法與select()的區別在於,是非阻塞的,即當前操作即使沒有通道準備好也是立即返回。只是返回的是0.
    值得注意的是:呼叫這個方法會清除所有之前執行了wakeup方法的作用。

下面來看select()函式的具體實現:

首先我們來看一下這三個方法的實現:是在SelectorImpl這個類裡面:

public int select(long var1) throws IOException {
        if(var1 < 0L) {
            throw new IllegalArgumentException("Negative timeout");
        } else {
            return this.lockAndDoSelect(var1 == 0L?-1L:var1);
        }
}

public
int select() throws IOException { return this.select(0L); } public int selectNow() throws IOException { return this.lockAndDoSelect(0L); }

我們可以發現,這三個方法最終都是呼叫了
lockAndDoSelect(0L);
這個函式,這個函式也是在SelectorImpl這個類裡面實現的,原始碼如下:

private int lockAndDoSelect(long var1) throws IOException {
    synchronized(this) {
        //堅持selector是否已經打開了
        if(!this.isOpen()) {
            throw new ClosedSelectorException();
        } else {
            Set var4 = this.publicKeys;
            int var10000;
            //這裡用了雙重鎖來實現同步訪問,雙重鎖可能引起死鎖。
            synchronized(this.publicKeys) {
                Set var5 = this.publicSelectedKeys;
                synchronized(this.publicSelectedKeys) {
                    var10000 = this.doSelect(var1);
                }
            }
            return var10000;
        }
    }
}

這裡先看下這個isOpen()函式的具體實現,這個函式是在 AbstractSelector 中實現的。

public final boolean isOpen() {
    return selectorOpen.get();
}

函式的功能:檢查這個Selector是否開啟;
在isOpen()方法中的selectorOpen變數在AbstractSelector類中定義如下:

private AtomicBoolean selectorOpen = new AtomicBoolean(true);

即selectorOpen是一個原子性的變數;如果在執行selector.select()方法之前執行了Selector selector = Selector.open();則selectorOpen就進行了初始化,為true。否則為false。

最後分析一下lockAndDoSelect()中的核心函式:
doSelect(var1);
傳引數是var1是一個long型整數,表示阻塞等待的時間。
doselect()方法也是一個abstract方法。他有兩個實現:
這裡寫圖片描述
由於我是在Mac下,所以預設呼叫的是KQueueSelectorImpl這個類裡面的實現,原始碼如下:

protected int doSelect(long var1) throws IOException {
    boolean var3 = false;
    //判斷當前selector是否是關閉的,
    if(this.closed) {
        throw new ClosedSelectorException();
    } else {
        this.processDeregisterQueue();

        int var7;
        try {
            this.begin();
            var7 = this.kqueueWrapper.poll(var1);
        } finally {
            this.end();
        }

        this.processDeregisterQueue();
        return this.updateSelectedKeys(var7);
    }
}

這部分程式碼還是比較複雜的,這裡我只分析核心的地方:

var7 = this.kqueueWrapper.poll(var1);

這個函式的內部會呼叫系統的poll函式,輪詢kqueueWrapper裡面儲存的fd,內部會呼叫native方法。這裡會監聽fd中是否有資料進出,這回造成IO阻塞,直到有資料讀寫事件發生。