本文是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());』)
- 如設(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)閉。
- 如果沒有設(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)系)。
- 觸發(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
- 從『if (runAllTasks() || runShutdownHooks())』這個判斷語句中,我們能夠確保只有在taskQueue中所有的任務(wù)都被執(zhí)行完了,并且shutdownHooks集合中所有的shutdownHook也都執(zhí)行完了之后,這個判斷語句才會返回true。也就是說,當(dāng)該if語句返回true時,我們能夠確保所有的任務(wù)和shutdownHook都已經(jīng)執(zhí)行完了。
- 『nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout』:接下來我們判斷,執(zhí)行完上面所有任務(wù)(包括taskQueue中的任務(wù)、可執(zhí)行的定時/周期性任務(wù)、所有的shutdownHook任務(wù))所需的時間是否已經(jīng)超過了優(yōu)雅關(guān)閉的超時時間(gracefulShutdownTimeout),如果已經(jīng)超過了,那么則退出confirmShutdown方法,并返回true。否則,繼續(xù)下面的步驟
- 『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供用戶來更好更方便的去使用它。
若文章有任何錯誤,望大家不吝指教:)