哦,这就是java的优雅停机?(实现及原理)

简介:
优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊!

其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

再比如,就是不会让调用方的请求处理了一增,一下就中断了。而处理完本次后,再停止服务。

Java语言中,我们可以通过Runtime.getRuntime().addShutdownHook()方法来注册钩子,以保证程序平滑退出。(其他语言也类似)

来个栗子:

 
  1. public class ShutdownGracefulTest {


  2. /**

  3. * 使用线程池处理任务

  4. */

  5. public static ExecutorService executorService = Executors.newCachedThreadPool();


  6. public static void main(String[] args) {


  7. //假设有5个线程需要执行任务

  8. for(int i = 0; i < 5; i++){

  9. final int id = i;

  10. Thread taski = new Thread(new Runnable() {

  11. @Override

  12. public void run() {

  13. System.out.println(System.currentTimeMillis() + " : thread_" + id + " start...");

  14. try {

  15. TimeUnit.SECONDS.sleep(id);

  16. }

  17. catch (InterruptedException e) {

  18. e.printStackTrace();

  19. }

  20. System.out.println(System.currentTimeMillis() + " : thread_" + id + " finish!");

  21. }

  22. });

  23. taski.setDaemon(true);

  24. executorService.submit(taski);

  25. }


  26. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

  27. @Override

  28. public void run() {


  29. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown hooking...");

  30. boolean shutdown = true;

  31. try {

  32. executorService.shutdown();

  33. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " shutdown signal got, wait threadPool finish.");

  34. executorService.awaitTermination(1500, TimeUnit.SECONDS);

  35. boolean done = false;

  36. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " all thread's done.");

  37. }

  38. catch (InterruptedException e) {

  39. e.printStackTrace();

  40. // 尝试再次关闭

  41. if(!executorService.isTerminated()) {

  42. executorService.shutdownNow();

  43. }

  44. }

  45. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown done...");

  46. }

  47. }));


  48. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

  49. @Override

  50. public void run() {

  51. try {

  52. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown hooking...");

  53. Thread.sleep(1000);

  54. }

  55. catch (InterruptedException e) {

  56. e.printStackTrace();

  57. }

  58. System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown done...");

  59. }

  60. }));


  61. System.out.println("main method exit...");

  62. System.exit(0);

  63. }

  64. }

运行结果如下:

8ea56afe2068f191b9d08ae03d7e0fc5c5f6e3f5

很明显,确实是优雅了,虽然最后收到了一关闭信号,但是仍然保证了任务的处理完成。很棒吧!

那么,在实际应用中是如何体现优雅停机呢?

kill -15 pid

通过该命令发送一个关闭信号给到jvm, 然后就开始执行 Shutdown Hook 了,你可以做很多:

  1. 关闭 socket 链接

  2. 清理临时文件

  3. 发送消息通知给订阅方,告知自己下线

  4. 将自己将要被销毁的消息通知给子进程

  5. 各种资源的释放 ...

而在平时工作中,我们不乏看到很多运维同学,是这么干的:

kill -9 pid

如果这么干的话,jvm也无法了,kill -9 相当于一次系统宕机,系统断电。这会给应用杀了个措手不及,没有留给应用任何反应的机会。

所以,无论如何是优雅不起来了。

要优雅,是代码和运维的结合!

其中,线程池的关闭方式为:

 
  1. executorService.shutdown();

  2. executorService.awaitTermination(1500, TimeUnit.SECONDS);

ThreadPoolExecutor 在 shutdown 之后会变成 SHUTDOWN 状态,无法接受新的任务,随后等待正在执行的任务执行完成。意味着,shutdown 只是发出一个命令,至于有没有关闭还是得看线程自己。

ThreadPoolExecutor 对于 shutdownNow 的处理则不太一样,方法执行之后变成 STOP 状态,并对执行中的线程调用 Thread.interrupt() 方法(但如果线程未处理中断,则不会有任何事发生),所以并不代表“立刻关闭”。

  • shutdown() :启动顺序关闭,其中执行先前提交的任务,但不接受新任务。如果已经关闭,则调用没有附加效果。此方法不等待先前提交的任务完成执行。

  • shutdownNow():尝试停止所有正在执行的任务,停止等待任务的处理,并返回正在等待执行的任务的列表。当从此方法返回时,这些任务将从任务队列中耗尽(删除)。此方法不等待主动执行的任务终止。

  • executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的时间,防止任务无限期的运行(前面已经强调过了,即使是 shutdownNow 也不能保证线程一定停止运行)。

注意:

  • 虚拟机会对多个shutdownhook以未知的顺序调用,都执行完后再退出。

  • 如果接收到 kill -15 pid 命令时,执行阻塞操作,可以做到等待任务执行完成之后再关闭 JVM。同时,也解释了一些应用执行 kill -15 pid 无法退出的问题,如:中断被阻塞了,或者hook运行了死循环代码。

实现原理:

Runtime.getRuntime().addShutdownHook(hook); // 添加钩子,开启优雅之路

// 具体流程如下:

 
  1. /**

  2. * Registers a new virtual-machine shutdown hook.

  3. *

  4. * @param hook

  5. * An initialized but unstarted <tt>{@link Thread}</tt> object

  6. *

  7. * @throws IllegalArgumentException

  8. * If the specified hook has already been registered,

  9. * or if it can be determined that the hook is already running or

  10. * has already been run

  11. *

  12. * @throws IllegalStateException

  13. * If the virtual machine is already in the process

  14. * of shutting down

  15. *

  16. * @throws SecurityException

  17. * If a security manager is present and it denies

  18. * <tt>{@link RuntimePermission}("shutdownHooks")</tt>

  19. *

  20. * @see #removeShutdownHook

  21. * @see #halt(int)

  22. * @see #exit(int)

  23. * @since 1.3

  24. */

  25. public void addShutdownHook(Thread hook) {

  26. SecurityManager sm = System.getSecurityManager();

  27. if (sm != null) {

  28. sm.checkPermission(new RuntimePermission("shutdownHooks"));

  29. }

  30. // 添加到 application 中

  31. ApplicationShutdownHooks.add(hook);

  32. }


  33. // java.lang.ApplicationShutdownHooks.add(hook);

  34. static synchronized void add(Thread hook) {

  35. if(hooks == null)

  36. throw new IllegalStateException("Shutdown in progress");


  37. if (hook.isAlive())

  38. throw new IllegalArgumentException("Hook already running");


  39. if (hooks.containsKey(hook))

  40. throw new IllegalArgumentException("Hook previously registered");

  41. // hooks 以map类型保存, k->k 形式存储,保证每一个钩子都是独立的

  42. hooks.put(hook, hook);

  43. }


  44. // java.lang.ApplicationShutdownHooks 会先注册一个静态块,添加一个任务到 Shutdown 中

  45. /* The set of registered hooks */

  46. private static IdentityHashMap<Thread, Thread> hooks;

  47. static {

  48. try {

  49. Shutdown.add(1 /* shutdown hook invocation order */,

  50. false /* not registered if shutdown in progress */,

  51. new Runnable() {

  52. public void run() {

  53. // 即当该任务被调用时,调用自身的运行方法,使所有注册的 hook 运行起来

  54. runHooks();

  55. }

  56. }

  57. );

  58. hooks = new IdentityHashMap<>();

  59. } catch (IllegalStateException e) {

  60. // application shutdown hooks cannot be added if

  61. // shutdown is in progress.

  62. hooks = null;

  63. }

  64. }


  65. // runHooks 执行所有钩子线程,进行异步调用

  66. /* Iterates over all application hooks creating a new thread for each

  67. * to run in. Hooks are run concurrently and this method waits for

  68. * them to finish.

  69. */

  70. static void runHooks() {

  71. Collection<Thread> threads;

  72. synchronized(ApplicationShutdownHooks.class) {

  73. threads = hooks.keySet();

  74. hooks = null;

  75. }


  76. for (Thread hook : threads) {

  77. hook.start();

  78. }

  79. for (Thread hook : threads) {

  80. try {

  81. // 阻塞等待所有完成

  82. hook.join();

  83. } catch (InterruptedException x) { }

  84. }

  85. }

到现在为止,我们已经知道关闭钩子是如何执行的,但是,还不是知道,该钩子是何时触发?

 
  1. // java.lang.Shutdown.add() 该方法会jvm主动调用,从而触发 后续钩子执行

  2. /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon

  3. * thread has finished. Unlike the exit method, this method does not

  4. * actually halt the VM.

  5. */

  6. static void shutdown() {

  7. synchronized (lock) {

  8. switch (state) {

  9. case RUNNING: /* Initiate shutdown */

  10. state = HOOKS;

  11. break;

  12. case HOOKS: /* Stall and then return */

  13. case FINALIZERS:

  14. break;

  15. }

  16. }

  17. synchronized (Shutdown.class) {

  18. // 执行序列

  19. sequence();

  20. }

  21. }

  22. // 而 sequence() 则会调用 runHooks(), 调用自定义的钩子任务

  23. private static void sequence() {

  24. synchronized (lock) {

  25. /* Guard against the possibility of a daemon thread invoking exit

  26. * after DestroyJavaVM initiates the shutdown sequence

  27. */

  28. if (state != HOOKS) return;

  29. }

  30. runHooks();

  31. boolean rfoe;

  32. synchronized (lock) {

  33. state = FINALIZERS;

  34. rfoe = runFinalizersOnExit;

  35. }

  36. if (rfoe) runAllFinalizers();

  37. }


  38. // 执行钩子,此处最多允许注册 10 个钩子,且进行同步调用,当然这是最顶级的钩子,钩子下还可以添加钩子,可以任意添加n个

  39. private static void runHooks() {

  40. for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {

  41. try {

  42. Runnable hook;

  43. synchronized (lock) {

  44. // acquire the lock to make sure the hook registered during

  45. // shutdown is visible here.

  46. currentRunningHook = i;

  47. hook = hooks[i];

  48. }

  49. // 同步调用注册的hook, 即 前面看到 ApplicationShutdownHooks.runHooks()

  50. if (hook != null) hook.run();

  51. } catch(Throwable t) {

  52. if (t instanceof ThreadDeath) {

  53. ThreadDeath td = (ThreadDeath)t;

  54. throw td;

  55. }

  56. }

  57. }

  58. }

如此,整个关闭流程完美了。

简化为:

  1. 注册流程(应用主动调用):Runtime.addShutdownHook -> ApplicationShutdownHooks.add()/static -> java.lang.Shutdown.add()/shutdown()

  2. 执行流程(jvm自动调用):java.lang.Shutdown.shutdown()->sequence()->runHooks() -> ApplicationShutdownHooks.runHooks() -> hooks 最终


原文发布时间为:2018-11-05
本文作者:Java技术驿站
本文来自云栖社区合作伙伴“ Java技术驿站”,了解相关信息可以关注“ Java技术驿站”。
相关文章
|
30天前
|
安全 Java
Java并发编程—并发流程控制与AQS原理及相关源码解析
Java并发编程—并发流程控制与AQS原理及相关源码解析
49 0
|
1月前
|
运维 监控 Java
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深度剖析Java线程转储分析的开发指南
学习JVM需要一定的编程经验和计算机基础知识,适用于从事Java开发、系统架构设计、性能优化、研究学习等领域的专业人士和技术爱好者。
41 5
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深度剖析Java线程转储分析的开发指南
|
1月前
|
算法 Java 程序员
论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理
本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。 在总体设计上,该框架借鉴了Cilk工作窃取框架的核心理念。其核心技术主要聚焦于高效的任务队列构建和管理,以及工作线程的管理。经过实际性能测试,我们发现大多数程序的并行加速效果显著,但仍有优化空间,未来可能需要进一步研究改进方案。
31 3
论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理
|
1月前
|
存储 缓存 自然语言处理
Java中的String类:原理、设计思想和与数组比较的优势
Java中的String类:原理、设计思想和与数组比较的优势
17 0
|
1月前
|
存储 缓存 Java
深度解析Java中的Integer类原理及方法实现
深度解析Java中的Integer类原理及方法实现
48 0
|
1月前
|
存储 算法 Java
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地理解HashMap的工作原理,掌握其核心实现。
24 0
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
|
1月前
|
存储 安全 Java
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)
HashMap是基于Map接口构建的数据结构,它以键值对的形式存储元素,允许键和值都为null。由于键的唯一性,HashMap中只能有一个键为null。HashMap的特点是元素的无序性和不重复性。
39 1
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)
|
1月前
|
安全 Java
java多线程之Lock锁原理以及案例实现电影院卖票
java多线程之Lock锁原理以及案例实现电影院卖票
|
1月前
|
数据可视化 Java 程序员
谈谈java 反射机制以及反射机制的原理
谈谈java 反射机制以及反射机制的原理
|
1月前
|
安全 Java 程序员
Java Review - 并发编程_ThreadPoolExecutor原理&源码剖析
Java Review - 并发编程_ThreadPoolExecutor原理&源码剖析
32 0

热门文章

最新文章

相关产品

  • 云迁移中心