《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;
- select(long timeout)
select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(引數)。
這個方法並不能提供精確時間的保證,和當執行wait(long timeout)方法時並不能保證會延時timeout道理一樣。
這裡的timeout說明如下:
- 如果 timeout為正,則select(long timeout)在等待有通道被選擇時至多會阻塞timeout毫秒
- 如果timeout為零,則永遠阻塞直到有至少一個通道準備就緒。
- timeout不能為負數
- 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阻塞,直到有資料讀寫事件發生。