Netty 源碼解析 ——— Netty 優(yōu)雅關(guān)閉流程

本文是Netty文集中“Netty 源碼解析”系列的文章。主要對Netty的重要流程以及類進(jìn)行源碼解析,以使得我們更好的去使用Netty。Netty是一個非常優(yōu)秀的網(wǎng)絡(luò)框架,對其源碼解讀的過程也是不斷學(xué)習(xí)的過程。

Netty的優(yōu)雅關(guān)閉操作

Netty是通過『eventLoopGroup.shutdownGracefully()』操作來實(shí)現(xiàn)它的優(yōu)雅關(guān)閉的。

我們先來看下shutdownGracefully方法的doc說明:

    /**
     * Signals this executor that the caller wants the executor to be shut down.  Once this method is called,
     * {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down.
     * Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
     * (usually a couple seconds) before it shuts itself down.  If a task is submitted during the quiet period,
     * it is guaranteed to be accepted and the quiet period will start over.
     *
     * @param quietPeriod the quiet period as described in the documentation
     * @param timeout     the maximum amount of time to wait until the executor is {@linkplain #shutdown()}
     *                    regardless if a task was submitted during the quiet period
     * @param unit        the unit of {@code quietPeriod} and {@code timeout}
     *
     * @return the {@link #terminationFuture()}
     */
    Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);

調(diào)用者希望執(zhí)行器進(jìn)行關(guān)閉的信號。一旦這個方法被調(diào)用了,『isShuttingDown()』方法將開始都會返回true,同時執(zhí)行器準(zhǔn)備關(guān)閉它自己。不像『shutdown()』方法,優(yōu)雅關(guān)閉會確保在它關(guān)閉它自己之前沒有任務(wù)在’the quiet period’(平靜期,即,gracefulShutdownQuietPeriod屬性)內(nèi)提交。如果一個任務(wù)在平靜期內(nèi)提交了,它會保證任務(wù)被接受并且重新開始平靜期。
如果你現(xiàn)在,對這段描述有些許困惑,沒關(guān)系,請繼續(xù)往下看,gracefulShutdownQuietPeriod(即,quietPeriod參數(shù))、gracefulShutdownStartTime(即,timeout參數(shù))主要會在『confirmShutdown()』方法中使用,下面會結(jié)合方法的實(shí)現(xiàn)場景來說明gracefulShutdownStartTime、gracefulShutdownQuietPeriod的含義。

源碼解析

    // AbstractEventExecutorGroup#shutdownGracefully
    public Future<?> shutdownGracefully() {
        return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
    }

    static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;
    static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;

    // MultithreadEventExecutorGroup#shutdownGracefully
    public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
        for (EventExecutor l: children) {
            l.shutdownGracefully(quietPeriod, timeout, unit);
        }
        return terminationFuture();
    }

遍歷EventExecutor[]數(shù)組,取出EventExecutor執(zhí)行shutdownGracefully操作。因?yàn)閮?yōu)雅關(guān)閉的流程主要是在各個NioEventLoop線程各自完成的,它是一個異步操作,因此此時返回該異步操作的Future,它是一個無返回結(jié)果的DefaultPromise對象。



① 確保 quietPeriod、unit的為有效值,即『quietPeriod >= 0』、『unit != null』。同時,確保timeout、quietPeriod之間的正確性,即『quietPeriod <= timeout』。
② 如果該NioEventLoop已經(jīng)執(zhí)行過關(guān)閉操作了,可能是『shutdownGracefully()』這樣的優(yōu)雅關(guān)閉,也有可能是『shutdown() or shutdownNow()』,當(dāng)然后兩種方法已經(jīng)不建議使用了(Deprecated)。那么直接返回該異步操作的Future對象。
③ 使用自旋鎖(『自旋 + CAS』)的方式修改當(dāng)前NioEventLoop所關(guān)聯(lián)的線程的狀態(tài)(volatile修飾的成員變量state)。因?yàn)榇朔椒赡鼙欢嗑€程同時調(diào)用,所以使用了自旋鎖的方式來保證NioEventLoop所關(guān)聯(lián)的線程狀態(tài)(state成員變量)的修改是原子性的。
之前我們說過,NioEventLoop所關(guān)聯(lián)的線程總共有5個狀態(tài),分別是:

private static final int ST_NOT_STARTED = 1;    // 線程還未啟動
private static final int ST_STARTED = 2;        // 線程已經(jīng)啟動
private static final int ST_SHUTTING_DOWN = 3;  // 線程正在關(guān)閉
private static final int ST_SHUTDOWN = 4;       // 線程已經(jīng)關(guān)閉
private static final int ST_TERMINATED = 5;     // 線程已經(jīng)終止

其中,在正常的線程狀態(tài)流為:ST_NOT_STARTED ——> ST_STARTED ——> ST_SHUTTING_DOWN ——> ST_TERMINATED。
而ST_SHUTDOWN這個線程狀態(tài)是已經(jīng)棄用的『shutdown() or shutdownNow()』所會設(shè)置的線程狀態(tài),但是無論怎樣在此步驟中,線程的狀態(tài)至少為會置為ST_SHUTTING_DOWN,或者說正常情況下都是會設(shè)置為ST_SHUTTING_DOWN的。
補(bǔ)充簡單說明下兩個知識點(diǎn):
a) 自旋鎖(Spin lock):由它自己去占有CPU運(yùn)行的時間,然后去嘗試進(jìn)行更新,直到更新成功完成。也因?yàn)樗钦加肅PU資源的方式,所以自旋鎖實(shí)現(xiàn)的操作是非常簡短的,不然其他線程可能會一直在自旋等待該自旋鎖。也正式因?yàn)樽孕i是不會釋放CPU的,也就是線程無需被掛起,這樣就沒有線程上下文切換的問題了。
因此,自旋鎖一般用于在多核處理器中預(yù)計(jì)線程持有鎖的時間很短(即鎖操作所需的時間非常的短)情況,甚至?xí)r間短于兩次線程上下文的切換的開銷。
b) volatile的可見性:volatile除了保證單個變量的讀/寫具有原子性外,還有有一個很重要的特性就是對線程內(nèi)存可見性的保證(即,對一個 volatile 變量的讀,總是能看到(任意線程)對這個 volatile 變量最后的寫入)。因?yàn)榇颂幮薷膕tate字段(本文是Netty服務(wù)端主線程)的線程和使用該字段的線程(NioEventLoop所關(guān)聯(lián)線程)不是同一個線程。因此通過volatile來修飾state字段來實(shí)現(xiàn),通過主線程修改了EventLoop所關(guān)聯(lián)的線程狀態(tài)后,在NioEventLoop的事件循環(huán)中能立即正確感知其線程狀態(tài)的變化,從而做出相應(yīng)的操作。
④ 根據(jù)傳入的參數(shù),設(shè)置成員變量gracefulShutdownQuietPeriod、gracefulShutdownTimeout。這里分別為默認(rèn)值,gracefulShutdownQuietPeriod為2秒,gracefulShutdownTimeout為15秒。
⑤ 如果NioEventLoop所關(guān)聯(lián)的線程之前的狀態(tài)為ST_NOT_STARTED,則說明該線程還未被啟動過,那么啟動該線程。
Q:為什么我們在執(zhí)行關(guān)閉操作的時候,還需要特意去啟動那些未啟動的NioEventLoop線程了?
A:是這樣的,在基于NIO的網(wǎng)絡(luò)傳輸模式中,會在構(gòu)建NioEventLoopGroup的時候就預(yù)先將一定數(shù)量的NioEventLoop給創(chuàng)建好(默認(rèn)為操作系統(tǒng)可運(yùn)行處理器數(shù)的2倍),而NioEventLoop在初始化的時候就會將其上的Selector給開啟了。同時Selector的關(guān)閉是在『doStartThread()』方法中最后會去完成的事。關(guān)于『doStartThread()』方法將在后面詳細(xì)展開。

好了,在完成將NioEventLoop所關(guān)聯(lián)的線程狀態(tài)修改為’ST_SHUTTING_DOWN’,也就說明關(guān)閉流程的開始。那么,接下來我們來看看NioEventLoop中是如果完成優(yōu)雅的關(guān)閉的。

我們先來看看doStartThread()方法:

    private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    for (;;) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }

                    // Check if confirmShutdown() was called at the end of the loop.
                    if (success && gracefulShutdownStartTime == 0) {
                        logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
                                "before run() implementation terminates.");
                    }

                    try {
                        // Run all remaining tasks and shutdown hooks.
                        for (;;) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }
                    } finally {
                        try {
                            cleanup();
                        } finally {
                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                            threadLock.release();
                            if (!taskQueue.isEmpty()) {
                                logger.warn(
                                        "An event executor terminated with " +
                                                "non-empty task queue (" + taskQueue.size() + ')');
                            }

                            terminationFuture.setSuccess(null);
                        }
                    }
                }
            }
        });
    }

① 這里executor.execute方法底層會通過ThreadPerTaskExecutor.execute(Runnable)方法來創(chuàng)建并啟動執(zhí)行任務(wù)的唯一線程。然后啟動的線程就會執(zhí)行我們通過executor.execute方法提交上來的這個任務(wù)(具體的這塊說明請見Netty 源碼解析 ——— 服務(wù)端啟動流程 (上))。
② 在Runnable任務(wù)中,會將當(dāng)前的線程設(shè)置為NioEventLoop所關(guān)聯(lián)的線程,即對成員變量thread賦值為Thread.currentThread()。然后執(zhí)行『SingleThreadEventExecutor.this.run();』這里實(shí)際調(diào)用的是『NioEventLoop#run()』方法來進(jìn)行事件循環(huán)操作。
③ 當(dāng)事件循環(huán)操作退出后(當(dāng)NioEventLoop需要關(guān)閉時,事件循環(huán)才會退出),進(jìn)行關(guān)閉的后續(xù)操作。

當(dāng)NioEventLoop已經(jīng)處于使用狀態(tài)(即,上面有Channel與其綁定),那么此時它會處于事件循環(huán)操作中;若NioEventLoop沒有處于使用狀態(tài)(即,該NioEventLoop已經(jīng)被初始化構(gòu)建好了,但還沒有任何一個Channel與其綁定過),那么在執(zhí)行shutdownGracefully()后,也會因?yàn)檎{(diào)用了doStartThread()方法,此時該NioEventLoop也會處于事件循環(huán)中。
那么,接下來我們就來看看NioEventLoop中事件循環(huán)對于優(yōu)雅關(guān)閉都完成了哪些操作了?

『NioEventLoop#run()』:

    protected void run() {
        for (;;) {
            try {
                ......
            } catch (Throwable t) {
                handleLoopException(t);
            }
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

此處,我們僅對與優(yōu)雅關(guān)閉流程相關(guān)的部分進(jìn)行展開。
事件循環(huán)首先會對Selector上注冊的Channel所就緒的I/O事件做處理,然后處理taskQueue中的任務(wù)以及時間已經(jīng)到達(dá)的定時/周期性任務(wù)。最后,在每次事件循環(huán)的最后都會判斷一次當(dāng)前的線程狀態(tài),如果發(fā)現(xiàn)當(dāng)前的線程狀態(tài)處于正在關(guān)閉的狀態(tài)(即,state >= ST_SHUTTING_DOWN)則會開始處理關(guān)閉流程,即:

    // Always handle shutdown even if the loop processing threw an exception.
    try {
        if (isShuttingDown()) {
            closeAll();
            if (confirmShutdown()) {
                return;
            }
        }
    } catch (Throwable t) {
        handleLoopException(t);
    }

注意,事件循環(huán)中將正常的工作流程放在了一個try-catch中,將關(guān)閉流程放在了另一個try-catch中,這是為了它們之間能夠不會互相影響。這樣即便工作流程拋出異常了,每次事件循環(huán)的最后依舊能夠去處理關(guān)閉事件。

關(guān)閉流程主要分為兩步:
① 『closeAll()』:

    private void closeAll() {
        selectAgain();
        Set<SelectionKey> keys = selector.keys();
        Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
        for (SelectionKey k: keys) {
            Object a = k.attachment();
            if (a instanceof AbstractNioChannel) {
                channels.add((AbstractNioChannel) a);
            } else {
                k.cancel();
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                invokeChannelUnregistered(task, k, null);
            }
        }

        for (AbstractNioChannel ch: channels) {
            ch.unsafe().close(ch.unsafe().voidPromise());
        }
    }

獲取該注冊到這個Selector所有Channel所對應(yīng)的SelectionKey,然后獲取SelectionKey附加對象attachment(),若attachment是一個AbstractNioChannel對象則先讓放入到channels集合中,否則直接調(diào)用『k.cancel(),即selectionKey.cancel()』操作將這個SelectableChannel從Selector上注銷。最后遍歷channels集合,依次取出AbstractNioChannel,進(jìn)行AbstractNioChannel的關(guān)閉操作(『ch.unsafe().close(ch.unsafe().voidPromise());』)


  1. 如設(shè)置了Socket#SO_LINGER配置項(xiàng)(即,config().getSoLinger() > 0),則說明當(dāng)需要關(guān)閉socket時,如果這時send buffer里還有數(shù)據(jù)沒有發(fā)送完,則先嘗試把send buffer中的數(shù)據(jù)發(fā)送完了再關(guān)閉socket。所以此時會先執(zhí)行doDeregister()操作,將當(dāng)前的SocketChannel從Selector上注銷,然后將close()操作作為一個任務(wù)放到另一個執(zhí)行器去執(zhí)行,也就是說不在當(dāng)前的NioEventLoop的線程上去執(zhí)行當(dāng)前SocketChannel的關(guān)閉操作,因?yàn)榇藭rSocketChannel不會馬上關(guān)閉,它需要嘗試在l_linger time時間內(nèi)將發(fā)送緩存區(qū)中的數(shù)據(jù)發(fā)送出去并等待對方的確認(rèn)。在l_linger time時間之后socket才會真正的被關(guān)閉。
  2. 如果沒有設(shè)置Socket#SO_LINGER配置項(xiàng),則直接在NioEventLoop線程上進(jìn)行SocketChannel/ServerSocektChannel的close()操作。并將outboundBuffer中所有還未發(fā)送出去的消息標(biāo)志為操作失敗(fail flush),然后關(guān)閉outboundBuffer,釋放相關(guān)資源。在關(guān)閉socket之后,將SocketChannel/ServerSocketChannel從Selector上注銷(即,『selectionKey.cancel()』。selectionKey表示一個SocketChannel/ServerSocketChannel注冊到Selector的關(guān)聯(lián)關(guān)系)。
  3. 觸發(fā)‘channelInactive’事件和‘channelUnregistered’事件,這兩個事件都會在ChannelPipeline中得以傳播。但這兩個事件的觸發(fā)會被封裝為一個任務(wù)提交至當(dāng)前的NioEventLoop的taskQueue在隨后被執(zhí)行,這么做的原因是為了確保eventLoop 的close 操作不會因?yàn)檎{(diào)用’channelInactive’事件和‘channelUnregistered’事件而“堵塞”,因?yàn)檫@里的close操作涉及到 socket 的關(guān)閉和 selectionKey.cancel() 操作,這兩步涉及NIO 網(wǎng)絡(luò)的操作是很重要的。‘channelInactive’事件和‘channelUnregistered’事件都是入站事件,它們會依次順序調(diào)用ChannelPipeline中的ChannelInboundHandler的channelInactive()方法以及channelUnregistered()方法。并且,ChannelPipeline中的head在處理‘channelUnregistered’事件時除了將該事件傳播給ChannelPipeline中的下一個ChannelInboundHandler外,還會觸發(fā)一個destroy()操作
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelUnregistered();

            // Remove all handlers sequentially if channel is closed and unregistered.
            if (!channel.isOpen()) {
                destroy();
            }
        }

該destroy()操作會刪除ChannelPipeline中的所有的handler(除了head、tail之外),并觸發(fā)每個Handler的handlerRemoved()方法。注意,這里handler的移除操作是先順序移除head到tail間所有的ChannelInboundHandler,然后在順序移除tail到head間所有的ChannelOutboundHandler。




② 『confirmShutdown()』:

protected boolean confirmShutdown() {
    if (!isShuttingDown()) {
        return false;
    }

    if (!inEventLoop()) {
        throw new IllegalStateException("must be invoked from an event loop");
    }

    cancelScheduledTasks();

    if (gracefulShutdownStartTime == 0) {
        gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
    }

    if (runAllTasks() || runShutdownHooks()) {
        if (isShutdown()) {
            // Executor shut down - no new tasks anymore.
            return true;
        }

        // There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period or
        // terminate if the quiet period is 0.
        // See https://github.com/netty/netty/issues/4241
        if (gracefulShutdownQuietPeriod == 0) {
            return true;
        }
        wakeup(true);
        return false;
    }

    final long nanoTime = ScheduledFutureTask.nanoTime();

    if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
        return true;
    }

    if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
        // Check if any tasks were added to the queue every 100ms.
        // TODO: Change the behavior of takeTask() so that it returns on timeout.
        wakeup(true);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // Ignore
        }

        return false;
    }

    // No tasks were added for last quiet period - hopefully safe to shut down.
    // (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
    return true;
}

首先,先簡單的描述下『runAllTasks()』和『runShutdownHooks()』所會完成的操作:
a) runAllTasks():首先會將已經(jīng)到運(yùn)行時間的定時/周期性任務(wù)放入taskQueue中,然后依次執(zhí)行taskQueue中的任務(wù)。當(dāng)且僅當(dāng)taskQueue中的任務(wù)都執(zhí)行完了,該方法會返回true,并且會將最后一個任務(wù)執(zhí)行完后此時的系統(tǒng)時間賦值為成員變量lastExecutionTime;否則,如果該taskQueue中沒有要執(zhí)行的任務(wù),那么該方法會返回false。
b) runShutdownHooks():執(zhí)行用戶自定義的所有shutdownHook,比如我們通過(『nioEventloop.addShutdownHook(runnable)』方法來提交我們希望該NioEventLoop被關(guān)閉時所要執(zhí)行的一些操作)。當(dāng)shutdownHook都執(zhí)行完了該方法會返回true,并且會在執(zhí)行完最后一個showdownHook后將此時的系統(tǒng)時間賦值為成員變量lastExecutionTime;否則,如果沒有任何需要執(zhí)行的shutdownHook,即shutdownHooks集合為空,那么該方法將返回false。

接下來,我們來判斷在什么條件下confirmShutdown()方法將返回true,以至于可以退出NioEventLoop的事件循環(huán),繼續(xù)doStartThread()的后續(xù)操作以完成最后的優(yōu)雅關(guān)閉流程。
我們分兩種情況來討論:
① gracefulShutdownQuietPeriod == 0
如果taskQueue中待執(zhí)行的任務(wù),或者有到期的定時/周期性任務(wù),再或者有用戶自定義的shutdownHook任務(wù),那么會在執(zhí)行完任務(wù)后退出confirmShutdown方法,并返回true;否則,如果沒有任務(wù)待執(zhí)行的任務(wù),那么‘nanoTime - lastExecutionTime > gracefulShutdownQuietPeriod’也會使得confirmShutdown()方法退出,并返回true。

② gracefulShutdownQuietPeriod > 0

  1. 從『if (runAllTasks() || runShutdownHooks())』這個判斷語句中,我們能夠確保只有在taskQueue中所有的任務(wù)都被執(zhí)行完了,并且shutdownHooks集合中所有的shutdownHook也都執(zhí)行完了之后,這個判斷語句才會返回true。也就是說,當(dāng)該if語句返回true時,我們能夠確保所有的任務(wù)和shutdownHook都已經(jīng)執(zhí)行完了。
  2. 『nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout』:接下來我們判斷,執(zhí)行完上面所有任務(wù)(包括taskQueue中的任務(wù)、可執(zhí)行的定時/周期性任務(wù)、所有的shutdownHook任務(wù))所需的時間是否已經(jīng)超過了優(yōu)雅關(guān)閉的超時時間(gracefulShutdownTimeout),如果已經(jīng)超過了,那么則退出confirmShutdown方法,并返回true。否則,繼續(xù)下面的步驟
  3. 『nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod』:如果‘當(dāng)前時間距離最后一次執(zhí)行任務(wù)的時間’小于等于’優(yōu)雅退出的平靜期(gracefulShutdownQuietPeriod)’。則使NioEventLoop線程睡眠100ms后,退出confirmShutdown方法,并返回false,這時說明關(guān)閉操作是未被批準(zhǔn)的,那么NioEventLoop的事件循環(huán)并不會退出,并且會在下次事件循的最后再次調(diào)用confirmShutdown()方法進(jìn)行關(guān)閉操作的確認(rèn),也就是會從新執(zhí)行步驟1;否則,如果‘當(dāng)前時間距離最后一次執(zhí)行任務(wù)的時間’大于’優(yōu)雅退出的平靜期(gracefulShutdownQuietPeriod)’,則退出confirmShutdown方法,并返回true。此時說明,在一個優(yōu)雅退出的平靜期(gracefulShutdownQuietPeriod)內(nèi)都沒有任何的任務(wù)被提交至該NioEventLoop線程上,那么我們就有希望能夠安全的進(jìn)行關(guān)閉。為什么說是有希望了?這是因?yàn)槲覀儗?shí)在沒有辦法保證在此時用戶不會通過execute()來提交一個任務(wù)。


    我們用一個流程圖來說明gracefulShutdownQuietPeriod、gracefulShutdownTimeout在confirmShutdown操作中起到的作用和關(guān)系(注意,下面并不是confirmShutdown()方法流程圖):



    好了,在結(jié)束NioEventLoop的事件循環(huán)后,我們繼續(xù)來看doStartThread()的后續(xù)操作。

    首先會將變量success設(shè)置為true,接下就是執(zhí)行finally塊中的代碼了:
    ① 如果當(dāng)前NioEventLoop線程的狀態(tài)還不是處于關(guān)閉相關(guān)的狀態(tài)的話,則通過自旋鎖的方式將當(dāng)前NioEventLoop線程的狀態(tài)修改為’ST_SHUTTING_DOWN’。從我們當(dāng)前優(yōu)雅關(guān)閉的流程來說,當(dāng)前NioEventLoop線程的此時就是ST_SHUTTING_DOWN了。
    ② 判斷,如果NioEventLoop事件循環(huán)結(jié)束了,但是‘gracefulShutdownStartTime’成員變量卻為0,則說明事件循環(huán)不是因?yàn)閏onfirmShutdown()方法而導(dǎo)致的結(jié)束,那么就打印一個錯誤日志,告知當(dāng)前的EventExecutor的實(shí)現(xiàn)是由問題的,因?yàn)槭录h(huán)的終止必須是通過調(diào)用confirmShutdown()方法來實(shí)現(xiàn)的,也就是說,事件循環(huán)能夠正確退出,也就是因?yàn)殛P(guān)閉操作被確認(rèn)了。
    ③ 此時會通過自旋鎖的方式再次調(diào)用一次『confirmShutdown()』,以確保所有的NioEventLoop中taskQueue中所有的任務(wù)以及用戶自定義的所有shutdownHook也都執(zhí)行了。之后才會進(jìn)行關(guān)閉操作。
    ④ cleanup():
    protected void cleanup() {
        try {
            selector.close();
        } catch (IOException e) {
            logger.warn("Failed to close a selector.", e);
        }
    }

會將當(dāng)前NioEventLoop所關(guān)聯(lián)的Selector關(guān)閉。
⑤ 修改NioEventLoop線程的狀態(tài)為’ST_TERMINATED’。注意,在此操作完成之后,所有提交至該NioEventLoop顯示的任務(wù)都會被拒絕,也就是該NioEventLoop不會再接收任何的任務(wù)了。

protected void addTask(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    if (!offerTask(task)) {
        reject(task);
    }
}

final boolean offerTask(Runnable task) {
    if (isShutdown()) {
        reject();
    }
    return taskQueue.offer(task);
}

public boolean isShutdown() {
    return state >= ST_SHUTDOWN;
}

⑥ threadLock.release():threadLock是一個初始化資源為0的信號量,此操作會使得信號量的資源+1。那么這種情況下,如果有用戶操作了awaitTermination方法的話(該方法底層會通過『threadLock.tryAcquire(timeout, unit)』來阻塞的嘗試獲取信號量的資源),該方法就會結(jié)束阻塞并返回,當(dāng)然它也可以因?yàn)樵O(shè)置的等待超時間已到而返回。
⑦ 此時會再次判斷該NioEventLoop的taskQueue是否為空,如果為非空,只會打印警告日志,告知用戶,當(dāng)前NioEventLoop在退出時仍有未完成的任務(wù)。而這個任務(wù)可能是在步驟③完成后,步驟⑤完成之前,又有用戶提交上來的。
⑧ 設(shè)置該優(yōu)雅關(guān)閉異步操作為成功完成。

后記

好了,整個NioEventLoopGroup的整個優(yōu)雅關(guān)閉流程就分析完了,一句簡單『nioEventLoopGroup.shutdownGracefully()』操作背后竟然有著如此復(fù)雜的關(guān)閉流程,再次佩服Netty為我們將復(fù)雜的流程給封閉化,而提供最為簡便的API供用戶來更好更方便的去使用它。
若文章有任何錯誤,望大家不吝指教:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內(nèi)容