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钩子中:

  1. public class ShutdownHook extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("Shut down signal received.");
  5. //do something.
  6. System.out.println("Shut down complete.");
  7. }
  8. public ShutdownHook() {
  9. Runtime.getRuntime().addShutdownHook(this);
  10. }
  11. }

       写个main来测试下:

  1. public class TestMain {
  2. private ShutdownHook shutdownHook;
  3. public TestMain() {
  4. this.shutdownHook = new ShutdownHook();
  5. }
  6. public static void main(String[] args) {
  7. TestMain app = new TestMain();
  8. System.out.println("start of main()");
  9. //do something.
  10. System.out.println("End of main()");
  11. }
  12. }

       使用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

  1. public class ShutdownHook extends Thread {
  2. public boolean isReceivedSignal = false;
  3. @Override
  4. public void run() {
  5. System.out.println("Shut down signal received.");
  6. this.isReceivedSignal = true;
  7. //do something.
  8. System.out.println("Shut down complete.");
  9. }
  10. public ShutdownHook() {
  11. Runtime.getRuntime().addShutdownHook(this);
  12. }
  13. }

       注意:这里多了一个变量isReceivedSignal ,在接收到shutdown的信号的时候,会执行run方法,然后执行完成之后,将变量置为true。

TestMain

  1. public class TestMain {
  2. private ShutdownHook shutdownHook;
  3. public TestMain() {
  4. this.shutdownHook = new ShutdownHook();
  5. }
  6. public void execute() {
  7. while(!shutdownHook.isReceivedSignal) {
  8. System.out.println("I am sleep");
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("I am not sleep");
  15. }
  16. System.out.println("end execute");
  17. }
  18. public static void main(String[] args) {
  19. TestMain app = new TestMain();
  20. System.out.println("start of main()");
  21. //do something.
  22. app.execute();
  23. System.out.println("End of main()");
  24. }
  25. }

 

说明: 在testmain类中添加了一个execute方法,此方法就是根据ShutdownHook中的变量来进行判断是否进行while循环的。

       使用run main在运行下,然后使用kill pid的方式进行关闭(我是mac电脑,终端就可以使用kill了)。

       对于kill pid 实际上等同于kill -15 pid(kill -s 15 pid),使用kill -l可以查看到所有的信号意思:

  1. $ kill -l
  2. 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
  3. 5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE
  4. 9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS
  5. 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
  6. 17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
  7. 21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU
  8. 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
  9. 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:

  1. $ jps
  2. 96512 Jps
  3. 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秒:

  1. @Override
  2. public void run() {
  3. System.out.println("Shut down signal received.");
  4. this.isReceivedSignal = true;
  5. //do something.
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println("Shut down complete.");
  12. }

 

       运行看一下效果:

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:

  1. public class ShutdownHook extends Thread {
  2. public boolean isReceivedSignal = false;
  3. private Thread mainThread;
  4. @Override
  5. public void run() {
  6. System.out.println("Shut down signal received.");
  7. this.isReceivedSignal = true;
  8. //do something.
  9. // try {
  10. // Thread.sleep(1000);
  11. // } catch (InterruptedException e) {
  12. // e.printStackTrace();
  13. // }
  14. try {
  15. mainThread.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println("Shut down complete.");
  20. }
  21. public ShutdownHook(Thread mainThread) {
  22. this.mainThread = mainThread;
  23. Runtime.getRuntime().addShutdownHook(this);
  24. }
  25. }

说明:构建方法接收一个mainThread,在run方法使用join等待主线程执行完毕。

TestMain

  1. public class TestMain {
  2. private ShutdownHook shutdownHook;
  3. public TestMain() {
  4. this.shutdownHook = new ShutdownHook(Thread.currentThread());
  5. }
  6. public void execute() {
  7. while(!shutdownHook.isReceivedSignal) {
  8. System.out.println("I am sleep");
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("I am not sleep");
  15. }
  16. System.out.println("end execute");
  17. }
  18. public static void main(String[] args) {
  19. TestMain app = new TestMain();
  20. System.out.println("start of main()");
  21. //do something.
  22. app.execute();
  23. System.out.println("End of main()");
  24. }
  25. }

说明: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:发出终止信号。

 

  1. 我就是我,是颜色不一样的烟火。
  2. 我就是我,是与众不同的小苹果。

    购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287