哦,這就是java的優(yōu)雅停機?(實現(xiàn)及原理)
優(yōu)雅停機?這個名詞我是服的,如果拋開專業(yè)不談,多好的名詞啊!
其實優(yōu)雅停機,就是在要關(guān)閉服務(wù)之前,不是立馬全部關(guān)停,而是做好一些善后操作,比如:關(guān)閉線程、釋放連接資源等。
再比如,就是不會讓調(diào)用方的請求處理了一增,一下就中斷了。而處理完本次后,再停止服務(wù)。
Java語言中,我們可以通過
Runtime.getRuntime().addShutdownHook()
方法來注冊鉤子,以保證程序平滑退出。(其他語言也類似)?
來個栗子:
publicclassShutdownGraceFullTest {
? ?/**
? ? * 使用線程池處理任務(wù)
? ? */
? ?publicstaticExecutorService executorService = Executors.newCachedThreadPool();
? ?publicstaticvoid main(String[] args) {
? ? ? ?//假設(shè)有5個線程需要執(zhí)行任務(wù)
? ? ? ?for(int i = 0; i < 5; i++){
? ? ? ? ? ?finalint id = i;
? ? ? ? ? ?Thread taski = newThread(newRunnable() {
? ? ? ? ? ? ? ?@Override
? ? ? ? ? ? ? ?publicvoid run() {
? ? ? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : thread_" + id + " start...");
? ? ? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ? ? ?TimeUnit.SECONDS.sleep(id);
? ? ? ? ? ? ? ? ? ?} catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : thread_" + id + " finish!");
? ? ? ? ? ? ? ?}
? ? ? ? ? ?});
? ? ? ? ? ?taski.setDaemon(true);
? ? ? ? ? ?executorService.submit(taski);
? ? ? ?}
? ? ? ?// 添加一個鉤子處理未完任務(wù)
? ? ? ?Runtime.getRuntime().addShutdownHook(newThread(newRunnable() {
? ? ? ? ? ?@Override
? ? ? ? ? ?publicvoid run() {
? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown hooking...");
? ? ? ? ? ? ? ?boolean shutdown = true;
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?executorService.shutdown();
? ? ? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + ?" shutdown signal got, wait threadPool finish.");
? ? ? ? ? ? ? ? ? ?executorService.awaitTermination(1500, TimeUnit.SECONDS);
? ? ? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + ?" all thread's done.");
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown done...");
? ? ? ? ? ?}
? ? ? ?}));
? ? ? ?// 多個關(guān)閉鉤子并發(fā)執(zhí)行
? ? ? ?Runtime.getRuntime().addShutdownHook(newThread(newRunnable() {
? ? ? ? ? ?@Override
? ? ? ? ? ?publicvoid run() {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown hooking...");
? ? ? ? ? ? ? ? ? ?Thread.sleep(1000);
? ? ? ? ? ? ? ?} catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown done...");
? ? ? ? ? ?}
? ? ? ?}));
? ? ? ?System.out.println("main method exit...");
? ? ? ?// 故意調(diào)用jvm退出命令,發(fā)送關(guān)閉信號,否則正常情況下 jvm 會等待最后一個非守護線程關(guān)閉才會退出
? ? ? ?System.exit(0);
? ?}
}
運行結(jié)果如下:

很明顯,確實是優(yōu)雅了,雖然最后收到了一關(guān)閉信號,但是仍然保證了任務(wù)的處理完成。很棒吧!
那么,在實際應(yīng)用中是如何體現(xiàn)優(yōu)雅停機呢?
kill -15 pid
通過該命令發(fā)送一個關(guān)閉信號給到j(luò)vm, 然后就開始執(zhí)行 Shutdown Hook 了,你可以做很多:
1、 關(guān)閉 socket 鏈接
2、 清理臨時文件
3、 發(fā)送消息通知給訂閱方,告知自己下線
4、 將自己將要被銷毀的消息通知給子進程
5、 各種資源的釋放
...
而在平時工作中,我們不乏看到很多運維同學(xué),是這么干的:
kill -9 pid
如果這么干的話,jvm也無法了,kill -9 相當(dāng)于一次系統(tǒng)宕機,系統(tǒng)斷電。這會給應(yīng)用殺了個措手不及,沒有留給應(yīng)用任何反應(yīng)的機會。
所以,無論如何是優(yōu)雅不起來了。
要優(yōu)雅,是代碼
其中,線程池的關(guān)閉方式為:
executorService.shutdown();
executorService.awaitTermination(1500, TimeUnit.SECONDS);
ThreadPoolExecutor 在 shutdown 之后會變成 SHUTDOWN 狀態(tài),無法接受新的任務(wù),隨后等待正在執(zhí)行的任務(wù)執(zhí)行完成。意味著,shutdown 只是發(fā)出一個命令,至于有沒有關(guān)閉還是得看線程自己。
ThreadPoolExecutor 對于 shutdownNow 的處理則不太一樣,方法執(zhí)行之后變成 STOP 狀態(tài),并對執(zhí)行中的線程調(diào)用 Thread.interrupt() 方法(但如果線程未處理中斷,則不會有任何事發(fā)生),所以并不代表“立刻關(guān)閉”。
shutdown() :啟動順序關(guān)閉,其中執(zhí)行先前提交的任務(wù),但不接受新任務(wù)。如果已經(jīng)關(guān)閉,則調(diào)用沒有附加效果。此方法不等待先前提交的任務(wù)完成執(zhí)行。
shutdownNow():嘗試停止所有正在執(zhí)行的任務(wù),停止等待任務(wù)的處理,并返回正在等待執(zhí)行的任務(wù)的列表。當(dāng)從此方法返回時,這些任務(wù)將從任務(wù)隊列中耗盡(刪除)。此方法不等待主動執(zhí)行的任務(wù)終止。
executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的時間,防止任務(wù)無限期的運行(前面已經(jīng)強調(diào)過了,即使是 shutdownNow 也不能保證線程一定停止運行)。
注意:
虛擬機會對多個shutdownhook以未知的順序調(diào)用,都執(zhí)行完后再退出。
如果接收到 kill -15 pid 命令時,執(zhí)行阻塞操作,可以做到等待任務(wù)執(zhí)行完成之后再關(guān)閉 JVM。同時,也解釋了一些應(yīng)用執(zhí)行 kill -15 pid 無法退出的問題,如:中斷被阻塞了,或者h(yuǎn)ook運行了死循環(huán)代碼。
出處:https://dwz.cn/nRS7c1Zg
—————END—————
更多精彩: Java實戰(zhàn)項目視頻,給需要的讀者,收藏! 27個阿里 Java 開源項目,值得收藏! 從0到1搭建后端架構(gòu)的經(jīng)歷 Spring Boot 注解大全,一鍵收藏! 差點被開除:一次訂單號重復(fù)的事故! 一款仿網(wǎng)易云音樂Java開源系統(tǒng)(附源碼) 關(guān)注公眾號,查看更多優(yōu)質(zhì)文章 最近,整理一份Java資料《Java從0到1》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。 獲取方式:關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多Java內(nèi)容陸續(xù)奉上。 明天見(??ω??)??
