jdk的nio之selector(3)

來看看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層面分析也就到這裏結束了。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章