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