設(shè)計(jì)Java應(yīng)用程序的平滑停止
Java應(yīng)用程序退出的觸發(fā)機(jī)制有:
- 自動(dòng)結(jié)束:應(yīng)用沒有存活線程或只有后臺(tái)線程時(shí);
- System.exit(0);
- kill 或 ctrl+C;
- kill -9 強(qiáng)制退出;
如何做到應(yīng)用程序平滑停止
程序的退出就像關(guān)機(jī)一樣,我們希望關(guān)機(jī)時(shí)平滑關(guān)機(jī),保證所有應(yīng)用程序的數(shù)據(jù)都保存了。就像現(xiàn)在在寫得blog,希望關(guān)機(jī)的時(shí)候能被保存好到草稿箱里。
我們的的Java程序中經(jīng)常有一種常駐的任務(wù)或服務(wù),如消息消費(fèi)端、服務(wù)提供者,我們期望停止也是平滑的不會(huì)出現(xiàn)事務(wù)執(zhí)行到一半產(chǎn)生臟數(shù)據(jù)。
java對(duì)這塊的支持是通過鉤子線程實(shí)現(xiàn)。每個(gè)Java進(jìn)程都可以注冊(cè)鉤子線程,鉤子線程程在程序退出的前被執(zhí)行(kill -9強(qiáng)制退出除外)。注冊(cè)鉤子線程代碼如下:
- Runtime.getRuntime().addShutdownHook(t);
我們可以在鉤子線程里做一些善后數(shù)據(jù)清理等事情,以保證程序是平滑退出的。
一般服務(wù)或框架運(yùn)行都要考慮其生命周期:
如spring容器的context.stop()方法。
再如線程池ExecutorService的shutdown方法,它會(huì)保證不接受新任務(wù),并把未執(zhí)行完的任務(wù)做完。
我們?cè)僭O(shè)計(jì)服務(wù)的時(shí)候也要考慮到停止時(shí)的stop方法,以便于退出時(shí)由鉤子線程調(diào)用。
注冊(cè)了鉤子線程后,程序收到退出信號(hào)后,會(huì)保持程序運(yùn)行,直到鉤子線程執(zhí)行完畢,才把程序的所有線程停止并退出,下面示例代碼可以說明這一點(diǎn):
- public class ShutDownTest {
- public static void main(String[] args) {
- //注冊(cè)***個(gè)鉤子
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- try {
- Thread.currentThread().sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("clean task1 completed.");
- }
- });
- //注冊(cè)第二個(gè)鉤子
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- try {
- Thread.currentThread().sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("clean task2 completed");
- }
- });
- //啟動(dòng)子線程
- new Thread() {
- public void run() {
- while (true) {
- try {
- Thread.currentThread().sleep(1000);
- System.out.println("sub thread is running");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- //程序退出
- System.exit(0);
- }
- }
程序輸出:
- sub thread is running
- sub thread is running
- sub thread is running
- sub thread is running
- clean task1 completed.
- sub thread is running
- sub thread is running
- sub thread is running
- sub thread is running
- sub thread is running
- clean task2 completed
注意點(diǎn):鉤子線程里只處理善后,目標(biāo)是盡可能快的退出且不保證有臟數(shù)據(jù)。如果鉤子線程里做過多事情,或者發(fā)生阻塞,那么可能出現(xiàn)kill失效,程序不能退出的情況,這是需要強(qiáng)制退出。
如以下程序會(huì)導(dǎo)致kill失效,需要強(qiáng)制退出,因?yàn)殂^子線程阻塞了:
- public class ShutDownTest {
- public static void main(String[] args) {
- //注冊(cè)鉤子
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- synchronized (ShutdownFileTest.class) {
- try {
- ShutdownFileTest.class.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- });
- //啟動(dòng)子線程
- new Thread() {
- public void run() {
- while (true) {
- try {
- Thread.currentThread().sleep(1000);
- System.out.println("sub thread is running");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- System.exit(0);
- }
- }
程序退出機(jī)制選擇
觸發(fā)程序退出的在前面已經(jīng)提到過,但是為了停止方便、安全和優(yōu)雅,一般我們推薦幾種操控性更強(qiáng)的退出機(jī)制。常見的推薦機(jī)制有以下幾種:
1.kill
在linux里用的比較多,向進(jìn)程發(fā)送退出信號(hào),java進(jìn)程收到后平滑退出。
2.shutdownfile
系統(tǒng)創(chuàng)建一個(gè)shutdown file.并監(jiān)聽shutdown file是否存在。如果發(fā)現(xiàn)shutdown file不存在了,那么調(diào)用System.exit,將程序退出。
如果期望只有特定的人才能終止該程序,那么你可以給文件設(shè)定權(quán)限,這樣就只有特定的人可以終止程序。
以下代碼是個(gè)簡(jiǎn)單的例子:
- import java.io.File;
- import java.io.IOException;
- public class ShutdownFileTest {
- public static void main(String[] args) {
- // 啟動(dòng)子線程
- new Thread() {
- public void run() {
- while (true) {
- try {
- Thread.currentThread().sleep(1000);
- System.out.println("sub thread is running");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- //啟動(dòng)shutdownfile監(jiān)聽線程
- new Thread() {
- public void run() {
- File shutDownFile = new File("a.shutdown");
- // create shut down file
- if (!shutDownFile.exists()) {
- try {
- shutDownFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // watch for file deleted then shutdown
- while (true) {
- try {
- if (shutDownFile.exists()) {
- Thread.currentThread().sleep(1000);
- } else {
- System.exit(0);
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- }
- }
3.打開一個(gè)端口,監(jiān)聽端口里的命令,收到命令后調(diào)用System.exit。
這個(gè)似乎不常見,也比較麻煩。
4.JMX
通過JMX的mbean遠(yuǎn)程控制來實(shí)現(xiàn)。
在這個(gè)鏈接里有看到例子:how-to-stop-java-process-gracefully
原文鏈接:http://singleant.iteye.com/blog/1441219
【編輯推薦】