Java语言的优雅停机 - 第308篇
一、何为优雅停机?
1.1 定义
什么叫优雅停机?简单说就是,在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。
应用接收到停止指令之后的步骤应该是:停止接收访问请求,等待已经接收到的请求处理完成,并能成功返回,这时才真正停止应用。
1.2 JVM Hook
Runtime.getRuntime().addShutdownHook(Thread hook):这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。
注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:
(1)程序正常退出;
(2)使用System.exit();
(3)终端使用Ctrl+C触发的中断;
(4)系统关闭;
(5)使用kill pid命令干掉进程;
1.3 JVM Hook的小例子1
编写一个代码继承Thread,并且使用Runtime.getRuntime().addShutdownHook(this) 将此线程添加到JVM的shutdown钩子中:
- public class ShutdownHook extends Thread {
-
- @Override
- public void run() {
- System.out.println("Shut down signal received.");
- //do something.
- System.out.println("Shut down complete.");
- }
-
- public ShutdownHook() {
- Runtime.getRuntime().addShutdownHook(this);
- }
- }
写个main来测试下:
- public class TestMain {
- private ShutdownHook shutdownHook;
-
- public TestMain() {
- this.shutdownHook = new ShutdownHook();
- }
-
- public static void main(String[] args) {
- TestMain app = new TestMain();
- System.out.println("start of main()");
- //do something.
- System.out.println("End of main()");
- }
- }
使用run main运行下,查看控制台:
start of main()
End of main()
Shut down signal received.
Shut down complete.
这种情况就是第一种情况(1)程序正常退出:执行完成main方法之后,程序就结束退出了,那么就会执行hook,也就是会执行线程的run()方法。
1.4 JVM Hook的小例子2
那么如何来模拟下使用kill pid的方式呐,很简单只要稍微修改下我们的代码。
我们一个核心的思路就是让main方法不要结束。我们可以在ShutdownHook定义一个是否接收到了shutdown的信号变量isReceivedSignal,然后在main方法执行有一个方法,通过此参数来循环执行即可,看代码:
ShutdownHook:
- public class ShutdownHook extends Thread {
-
- public boolean isReceivedSignal = false;
-
- @Override
- public void run() {
- System.out.println("Shut down signal received.");
- this.isReceivedSignal = true;
- //do something.
- System.out.println("Shut down complete.");
- }
-
- public ShutdownHook() {
- Runtime.getRuntime().addShutdownHook(this);
- }
- }
注意:这里多了一个变量isReceivedSignal ,在接收到shutdown的信号的时候,会执行run方法,然后执行完成之后,将变量置为true。
TestMain:
- public class TestMain {
- private ShutdownHook shutdownHook;
-
- public TestMain() {
- this.shutdownHook = new ShutdownHook();
- }
-
- public void execute() {
- while(!shutdownHook.isReceivedSignal) {
- System.out.println("I am sleep");
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("I am not sleep");
- }
- System.out.println("end execute");
- }
-
-
- public static void main(String[] args) {
- TestMain app = new TestMain();
- System.out.println("start of main()");
- //do something.
- app.execute();
- System.out.println("End of main()");
- }
- }
说明: 在testmain类中添加了一个execute方法,此方法就是根据ShutdownHook中的变量来进行判断是否进行while循环的。
使用run main在运行下,然后使用kill pid的方式进行关闭(我是mac电脑,终端就可以使用kill了)。
对于kill pid 实际上等同于kill -15 pid(kill -s 15 pid),使用kill -l可以查看到所有的信号意思:
- $ kill -l
- 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
- 5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE
- 9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS
- 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
- 17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
- 21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU
- 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
- 29) SIGINFO 30) SIGUSR1 31) SIGUSR2
kill pid和kill -s 15 pid含义一样,表示发送一个SIGTERM的信号给对应的程序。程序收到该信号后,将会发生以下事情:
(1)程序立刻停止;
(2)程序释放相应资源后立刻停止;
(3)程序可能仍然继续运行;
大部分程序在接收到SIGTERM信号后,会先释放自己的资源,然后再停止。但也有一些程序在收到信号后,做一些其他事情,并且这些事情是可以配置的。也就是说,SIGTERM多半是会被阻塞,忽略的。
对于kill -9 pid是我们常见的,意思是SIGKILL,表示强制、尽量终止一个进程。
来我们运行下kill pid(pid可以使用jps命令进行查看)。
查看进程ID:
- $ jps
- 96512 Jps
- 96510 TestMain
关闭进程TestMain:kill 96510。
查看控制台信息:
I am sleep
I am not sleep
I am sleep
Shut down signal received.
Shut down complete.
观察控制台的打印,我们不难发现:
(1)execute并未执行完;
(2)main方法也并未执行完;
所以这个kill之后,只能发出一个对于添加到了addShutdownHook的线程,对于主线程等同于就是直接结束了吗??
那么我们是否可以等待主线程执行完毕呐,答案是可以的,主要使用线程的join方法即可。
1.5 JVM Hook的小例子3
我们先猜想:对于主线程是否需是我们的ShuwdownHook的run方法执行太快了,然后JVM就认为全部执行完成了,就释放了资源,直接关闭了进程了呐。
猜想归猜想,可以简单验证下,在ShutdownHook的run方法,当接收到关闭信号的时候,先休眠个1秒:
- @Override
- public void run() {
- System.out.println("Shut down signal received.");
- this.isReceivedSignal = true;
- //do something.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Shut down complete.");
- }
运行看一下效果:
I am sleep
I am not sleep
I am sleep //进入while循环了
Shut down signal received. //接收到关闭信号,后sleep了.
I am not sleep //main thread继续执行
end execute //execute执行完毕
End of main() //跳出execute方法,回到main方法.
Shut down complete. //run方法执行完毕。
这种方式main是可以执行完毕了,但是我们很多时候,并不知道main方法还需要执行多久才能执行完毕,那么这个时候thread.join方法就派上用场了。
1.6 JVM Hook的小例子6
我们这个例子改造的核心思路就是,我们需要将主线程作为参数传到ShutdownHook类中,然后在ShutdownHook的run方法里执行Thread.join等待主线程执行完毕。
ShutdownHook:
- public class ShutdownHook extends Thread {
-
- public boolean isReceivedSignal = false;
- private Thread mainThread;
-
-
-
- @Override
- public void run() {
- System.out.println("Shut down signal received.");
- this.isReceivedSignal = true;
- //do something.
- // try {
- // Thread.sleep(1000);
- // } catch (InterruptedException e) {
- // e.printStackTrace();
- // }
-
- try {
- mainThread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Shut down complete.");
- }
-
- public ShutdownHook(Thread mainThread) {
- this.mainThread = mainThread;
- Runtime.getRuntime().addShutdownHook(this);
- }
- }
说明:构建方法接收一个mainThread,在run方法使用join等待主线程执行完毕。
TestMain:
- public class TestMain {
- private ShutdownHook shutdownHook;
-
- public TestMain() {
- this.shutdownHook = new ShutdownHook(Thread.currentThread());
- }
-
- public void execute() {
- while(!shutdownHook.isReceivedSignal) {
- System.out.println("I am sleep");
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("I am not sleep");
- }
- System.out.println("end execute");
- }
-
-
- public static void main(String[] args) {
- TestMain app = new TestMain();
- System.out.println("start of main()");
- //do something.
- app.execute();
- System.out.println("End of main()");
- }
- }
说明:TestMain没什么特殊之处,就是把当前自己所属的线程传给了ShutdownHook。
来run一下吧,使用kill pid关闭:
I am sleep
I am not sleep
I am sleep
Shut down signal received.
I am not sleep
end execute
End of main()
Shut down complete.
二、悟纤小结
师傅:我们今天就先介绍到这里,下节我们来说说Spring Boot的优雅停机方式。
悟纤:师傅棒棒的,剩下的就让徒儿来给师傅总结下。
(1)优雅停机?简单说就是,在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。
(2)Runtime.getRuntime().addShutdownHook(Thread hook):这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。
(3)对于kill指令-s 9和15是有区别的:-s 9:强制关闭进程;-s 15:发出终止信号。
- 我就是我,是颜色不一样的烟火。
- 我就是我,是与众不同的小苹果。
购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287