來看看selector最核心的select()方法吧
我們之前分析了Selector默認是WindowSelectorImpl的一個實例,調用selector(timeout),timout是給Selector設置的時間參數,之後調用lockAndDoSelector,lockAndDoSelector方法核心就是調用抽象方法doSelect,此時加了synchronize同步。我們看下子類的doSelect的實現,默認實現是在WindowsSelectorImpl類下。
protected int doSelect(long var1) throws IOException {
if(this.channelArray == null) {
throw new ClosedSelectorException();
} else {
this.timeout = var1;
this.processDeregisterQueue();
if(this.interruptTriggered) {
this.resetWakeupSocket();
return 0;
} else {
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try {
this.begin();
try {
this.subSelector.poll();
} catch (IOException var7) {
this.finishLock.setException(var7);
}
if(this.threads.size() > 0) {
this.finishLock.waitForHelperThreads();
}
} finally {
this.end();
}
this.finishLock.checkForException();
this.processDeregisterQueue();
int var3 = this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}
首先調用processDeregisterQueue()方法,把已經解除註冊的channel進行註銷註冊。
void processDeregisterQueue() throws IOException {
Set var1 = this.cancelledKeys();
synchronized(var1) {
if(!var1.isEmpty()) {
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();
try {
this.implDereg(var4);
} catch (SocketException var11) {
throw new IOException("Error deregistering key", var11);
} finally {
var3.remove();
}
}
}
}
}
遍歷加到cancelledKeys中的SelectorKey(需要解除註冊的key),依次調用implDereg()進行註銷。
protected void implDereg(SelectionKeyImpl var1) throws IOException {
int var2 = var1.getIndex();
assert var2 >= 0;
Object var3 = this.closeLock;
synchronized(this.closeLock) {
if(var2 != this.totalChannels - 1) {
SelectionKeyImpl var4 = this.channelArray[this.totalChannels - 1];
this.channelArray[var2] = var4;
var4.setIndex(var2);
this.pollWrapper.replaceEntry(this.pollWrapper, this.totalChannels - 1, this.pollWrapper, var2);
}
var1.setIndex(-1);
}
this.channelArray[this.totalChannels - 1] = null;
--this.totalChannels;
if(this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
--this.totalChannels;
--this.threadsCount;
}
this.fdMap.remove(var1);
this.keys.remove(var1);
this.selectedKeys.remove(var1);
this.deregister(var1);
SelectableChannel var7 = var1.channel();
if(!var7.isOpen() && !var7.isRegistered()) {
((SelChImpl)var7).kill();
}
}
如果當前解除註冊key的channel是Selector中最後一個,那麼直接移除,否則,需要將當前需移除的channel跟最後一個channel調換位置,再移除,以保證數組的連續性,剩下無非是註冊的逆操作。
完成真正註銷後,然後調用adjustThreadCount()
private void adjustThreadsCount() {
int var1;
if(this.threadsCount > this.threads.size()) {
for(var1 = this.threads.size(); var1 < this.threadsCount; ++var1) {
WindowsSelectorImpl.SelectThread var2 = new WindowsSelectorImpl.SelectThread(var1);
this.threads.add(var2);
var2.setDaemon(true);
var2.start();
}
} else if(this.threadsCount < this.threads.size()) {
for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
}
}
}
該方法是對於線程數量進行調整,要求的線程數量是根據channel數目統計的,每1024個channel註冊,則多一個線程來負責。根據現有線程進行動態調整。
之後調用begin()方法
protected final void begin() {
if (interruptor == null) {
interruptor = new Interruptible() {
public void interrupt(Thread ignore) {
AbstractSelector.this.wakeup();
}};
}
AbstractInterruptibleChannel.blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
interruptor.interrupt(me);
}
這裏先判斷interruptor是否爲空,空的話則創建一個,這個interruptor保證了:當前線程阻塞在io上時,當其被interruptor時,可以從select阻塞中被喚醒。在begin()完成後,將會調用其subSelector的poll,正式開始select過程。
private int poll() throws IOException {
return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress,
Math.min(WindowsSelectorImpl.this.totalChannels, 1024),
this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}
private int poll(int var1) throws IOException {
return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress +
(long)(this.pollArrayIndex * PollArrayWrapper.SIZE_POLLFD),
Math.min(1024, WindowsSelectorImpl.this.totalChannels - (var1 + 1) * 1024), this.readFds,
this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}
private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);
這裏的poll0()是native方法。主要是爲了監聽pollWrapper中所保存的fd是否有數據進出,如果沒有進出,則會在在此處在timeout的時間裏一直保持阻塞狀態。
當poll取得數據後,會重新檢查一遍已經被取消註冊的channel,並註銷它,之後調用updateSelectedKeys()
private int updateSelectedKeys() {
++this.updateCount;
byte var1 = 0;
int var4 = var1 + this.subSelector.processSelectedKeys(this.updateCount);
WindowsSelectorImpl.SelectThread var3;
for(Iterator var2 = this.threads.iterator(); var2.hasNext();
var4 += var3.subSelector.processSelectedKeys(this.updateCount)) {
var3 = (WindowsSelectorImpl.SelectThread)var2.next();
}
return var4;
}
這裏對所有的線程調用processSelectedKeys來處理所有線程在poll過程中取得的結果並處理。然後返回所有線程中處理的channel數量的總和。我們來看下processSelectedKeys()方法 private int processSelectedKeys(long var1) {
byte var3 = 0;
int var4 = var3 + this.processFDSet(var1, this.readFds, Net.POLLIN, false);
var4 += this.processFDSet(var1, this.writeFds, Net.POLLCONN | Net.POLLOUT, false);
var4 += this.processFDSet(var1, this.exceptFds, Net.POLLIN | Net.POLLCONN | Net.POLLOUT, true);
return var4;
}
private int processFDSet(long var1, int[] var3, int var4, boolean var5) {
int var6 = 0;
for(int var7 = 1; var7 <= var3[0]; ++var7) {
int var8 = var3[var7];
if(var8 == WindowsSelectorImpl.this.wakeupSourceFd) {
synchronized(WindowsSelectorImpl.this.interruptLock) {
WindowsSelectorImpl.this.interruptTriggered = true;
}
} else {
WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
if(var9 != null) {
SelectionKeyImpl var10 = var9.ski;
if(!var5 || !(var10.channel() instanceof SocketChannelImpl) ||
!WindowsSelectorImpl.this.discardUrgentData(var8)) {
if(WindowsSelectorImpl.this.selectedKeys.contains(var10)) {
if(var9.clearedCount != var1) {
if(var10.channel.translateAndSetReadyOps(var4, var10) && var9.updateCount != var1) {
var9.updateCount = var1;
++var6;
}
} else if(var10.channel.translateAndUpdateReadyOps(var4, var10)
&& var9.updateCount != var1) {
var9.updateCount = var1;
++var6;
}
var9.clearedCount = var1;
} else {
if(var9.clearedCount != var1) {
var10.channel.translateAndSetReadyOps(var4, var10);
if((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
WindowsSelectorImpl.this.selectedKeys.add(var10);
var9.updateCount = var1;
++var6;
}
} else {
var10.channel.translateAndUpdateReadyOps(var4, var10);
if((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
WindowsSelectorImpl.this.selectedKeys.add(var10);
var9.updateCount = var1;
++var6;
}
}
var9.clearedCount = var1;
}
}
}
}
}
return var6;
}
這裏首先根據poll監聽到發生io事件所需要處理的fd,根據fd從selector中讀取相應的已經註冊了的channel,根據所發生的io事件(讀,寫,異常)更新channel的狀態。相應的Selector的select到這裏算是結束了。
最後還想看一下WindowsSelectorImpl.wakeup()的具體實現
public Selector wakeup() {
Object var1 = this.interruptLock;
synchronized(this.interruptLock) {
if(!this.interruptTriggered) {
this.setWakeupSocket();
this.interruptTriggered = true;
}
return this;
}
}
private void setWakeupSocket() {
this.setWakeupSocket0(this.wakeupSinkFd);
}
private native void setWakeupSocket0(int var1);
最後找資料,windowsSelectorImpl.c中實現send(scoutFd, (char*)&POLLIN, 1, 0);這兒就跟我們之前selector.open中構造的pipe聯繫在了一起,向sink端寫入了一個自己,註冊在pollArray中的wakeupsource端的fd有變化,poll方法返回,並將線程從selector的select中喚醒。
小結
我覺得jdk 的nio的selector底層分析到這裏算是完結撒花了,相信看完這幾篇後那個圖應該算是掌握了。
如果有機會,要去jdk的native方法底層再一探nio的究竟。在jdk層面分析也就到這裏結束了。