java应用程序退出的触发机制有:
1.自动结束:应用没有存活线程或只有后台线程时;
2.System.exit(0);
3.kill 或 ctrl+C;
4.kill -9 强制退出;
如何做到应用程序平滑停止
程序的退出就像关机一样,我们希望关机时平滑关机,保证所有应用程序的数据都保存了。就像现在在写得blog,希望关机的时候能被保存好到草稿箱里。
我们的的java程序中经常有一种常驻的任务或服务,如消息消费端、服务提供者,我们期望停止也是平滑的不会出现事务执行到一半产生脏数据。
java对这块的支持是通过钩子线程实现。每个java进程都可以注册钩子线程,钩子线程程在程序退出的前被执行(kill -9强制退出除外)。注册钩子线程代码如下:
Runtime.getRuntime().addShutdownHook(t);
我们可以在钩子线程里做一些善后数据清理等事情,以保证程序是平滑退出的。
一般服务或框架运行都要考虑其生命周期:
如spring容器的context.stop()方法。
再如线程池ExecutorService的shutdown方法,它会保证不接受新任务,并把未执行完的任务做完。
我们再设计服务的时候也要考虑到停止时的stop方法,以便于退出时由钩子线程调用。
注册了钩子线程后,程序收到退出信号后,会保持程序运行,直到钩子线程执行完毕,才把程序的所有线程停止并退出,下面示例代码可以说明这一点:
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
===============================================================================
注意点 :钩子线程里只处理善后,目标是尽可能快的退出且不保证有脏数据。如果钩子线程里做过多事情,或者发生阻塞,那么可能出现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);
}
}
程序退出机制选择
触发程序退出的在前面已经提到过,但是为了停止方便、安全和优雅,一般我们推荐几种操控性更强的退出机制。常见的推荐机制有以下几种:
1.kill
在linux里用的比较多,向进程发送退出信号,java进程收到后平滑退出。
2.shutdownfile
系统创建一个shutdown file.并监听shutdown file是否存在。如果发现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监听线程
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.打开一个端口,监听端口里的命令,收到命令后调用System.exit。
这个似乎不常见,也比较麻烦。
4.JMX
通过JMX的mbean远程控制来实现。