哦,這就是Java的優(yōu)雅停機(jī)?(實(shí)現(xiàn)及原理)
優(yōu)雅停機(jī)? 這個(gè)名詞我是服的,如果拋開專業(yè)不談,多好的名詞啊!
其實(shí)優(yōu)雅停機(jī),就是在要關(guān)閉服務(wù)之前,不是立馬全部關(guān)停,而是做好一些善后操作,比如:關(guān)閉線程、釋放連接資源等。
再比如,就是不會(huì)讓調(diào)用方的請(qǐng)求處理了一增,一下就中斷了。而處理完本次后,再停止服務(wù)。
Java語(yǔ)言中,我們可以通過(guò)Runtime.getRuntime().addShutdownHook()方法來(lái)注冊(cè)鉤子,以保證程序平滑退出。(其他語(yǔ)言也類似)
來(lái)個(gè)栗子:
- public class ShutdownGraceFullTest {
- /**
- * 使用線程池處理任務(wù)
- */
- public static ExecutorService executorService = Executors.newCachedThreadPool();
- public static void main(String[] args) {
- //假設(shè)有5個(gè)線程需要執(zhí)行任務(wù)
- for(int i = 0; i < 5; i++){
- final int id = i;
- Thread taski = new Thread(new Runnable() {
- @Override
- public void 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);
- }
- // 添加一個(gè)鉤子處理未完任務(wù)
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void 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...");
- }
- }));
- // 多個(gè)關(guān)閉鉤子并發(fā)執(zhí)行
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void 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)閉信號(hào),否則正常情況下 jvm 會(huì)等待***一個(gè)非守護(hù)線程關(guān)閉才會(huì)退出
- System.exit(0);
- }
- }
運(yùn)行結(jié)果如下:
很明顯,確實(shí)是優(yōu)雅了,雖然***收到了一關(guān)閉信號(hào),但是仍然保證了任務(wù)的處理完成。很棒吧!
那么,在實(shí)際應(yīng)用中是如何體現(xiàn)優(yōu)雅停機(jī)呢?
- kill -15 pid
通過(guò)該命令發(fā)送一個(gè)關(guān)閉信號(hào)給到j(luò)vm, 然后就開始執(zhí)行 Shutdown Hook 了,你可以做很多:
- 關(guān)閉 socket 鏈接
- 清理臨時(shí)文件
- 發(fā)送消息通知給訂閱方,告知自己下線
- 將自己將要被銷毀的消息通知給子進(jìn)程
- 各種資源的釋放
- ...
而在平時(shí)工作中,我們不乏看到很多運(yùn)維同學(xué),是這么干的:
- kill -9 pid
如果這么干的話,jvm也無(wú)法了,kill -9 相當(dāng)于一次系統(tǒng)宕機(jī),系統(tǒng)斷電。這會(huì)給應(yīng)用殺了個(gè)措手不及,沒(méi)有留給應(yīng)用任何反應(yīng)的機(jī)會(huì)。
所以,無(wú)論如何是優(yōu)雅不起來(lái)了。
要優(yōu)雅,是代碼
其中,線程池的關(guān)閉方式為:
- executorService.shutdown();
- executorService.awaitTermination(1500, TimeUnit.SECONDS);
ThreadPoolExecutor 在 shutdown 之后會(huì)變成 SHUTDOWN 狀態(tài),無(wú)法接受新的任務(wù),隨后等待正在執(zhí)行的任務(wù)執(zhí)行完成。意味著,shutdown 只是發(fā)出一個(gè)命令,至于有沒(méi)有關(guān)閉還是得看線程自己。
ThreadPoolExecutor 對(duì)于 shutdownNow 的處理則不太一樣,方法執(zhí)行之后變成 STOP 狀態(tài),并對(duì)執(zhí)行中的線程調(diào)用 Thread.interrupt() 方法(但如果線程未處理中斷,則不會(huì)有任何事發(fā)生),所以并不代表“立刻關(guān)閉”。
shutdown() :?jiǎn)?dòng)順序關(guān)閉,其中執(zhí)行先前提交的任務(wù),但不接受新任務(wù)。如果已經(jīng)關(guān)閉,則調(diào)用沒(méi)有附加效果。此方法不等待先前提交的任務(wù)完成執(zhí)行。
shutdownNow():嘗試停止所有正在執(zhí)行的任務(wù),停止等待任務(wù)的處理,并返回正在等待執(zhí)行的任務(wù)的列表。當(dāng)從此方法返回時(shí),這些任務(wù)將從任務(wù)隊(duì)列中耗盡(刪除)。此方法不等待主動(dòng)執(zhí)行的任務(wù)終止。
executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的時(shí)間,防止任務(wù)***期的運(yùn)行(前面已經(jīng)強(qiáng)調(diào)過(guò)了,即使是 shutdownNow 也不能保證線程一定停止運(yùn)行)。
注意:
- 虛擬機(jī)會(huì)對(duì)多個(gè)shutdownhook以未知的順序調(diào)用,都執(zhí)行完后再退出。
- 如果接收到 kill -15 pid 命令時(shí),執(zhí)行阻塞操作,可以做到等待任務(wù)執(zhí)行完成之后再關(guān)閉 JVM。同時(shí),也解釋了一些應(yīng)用執(zhí)行 kill -15 pid 無(wú)法退出的問(wèn)題,如:中斷被阻塞了,或者h(yuǎn)ook運(yùn)行了死循環(huán)代碼。