|
|
51CTO旗下网站
|
|
移动端

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

优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊!其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

作者:佚名来源:等你归去来|2018-11-01 13:38

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

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

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

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

来个栗子:

  1. public class ShutdownGraceFullTest { 
  2.  
  3.     /** 
  4.      * 使用线程池处理任务 
  5.      */ 
  6.     public static ExecutorService executorService = Executors.newCachedThreadPool(); 
  7.  
  8.     public static void main(String[] args) { 
  9.  
  10.         //假设有5个线程需要执行任务 
  11.         for(int i = 0; i < 5; i++){ 
  12.             final int id = i; 
  13.             Thread taski = new Thread(new Runnable() { 
  14.                 @Override 
  15.                 public void run() { 
  16.                     System.out.println(System.currentTimeMillis() + " : thread_" + id + " start..."); 
  17.                     try { 
  18.                         TimeUnit.SECONDS.sleep(id); 
  19.                     } catch (InterruptedException e) { 
  20.                         e.printStackTrace(); 
  21.                     } 
  22.                     System.out.println(System.currentTimeMillis() + " : thread_" + id + " finish!"); 
  23.                 } 
  24.             }); 
  25.             taski.setDaemon(true); 
  26.             executorService.submit(taski); 
  27.         } 
  28.  
  29.         // 添加一个钩子处理未完任务 
  30.         Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 
  31.             @Override 
  32.             public void run() { 
  33.  
  34.                 System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown hooking..."); 
  35.                 boolean shutdown = true
  36.                 try { 
  37.                     executorService.shutdown(); 
  38.                     System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() +  " shutdown signal got, wait threadPool finish."); 
  39.                     executorService.awaitTermination(1500, TimeUnit.SECONDS); 
  40.                     System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() +  " all thread's done."); 
  41.                 } 
  42.                 catch (InterruptedException e) { 
  43.                     e.printStackTrace(); 
  44.                 } 
  45.                 System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown done..."); 
  46.             } 
  47.         })); 
  48.  
  49.         // 多个关闭钩子并发执行 
  50.         Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 
  51.             @Override 
  52.             public void run() { 
  53.                 try { 
  54.                     System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown hooking..."); 
  55.                     Thread.sleep(1000); 
  56.                 } catch (InterruptedException e) { 
  57.                     e.printStackTrace(); 
  58.                 } 
  59.                 System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown done..."); 
  60.             } 
  61.         })); 
  62.  
  63.         System.out.println("main method exit..."); 
  64.         // 故意调用jvm退出命令,发送关闭信号,否则正常情况下 jvm 会等待最后一个非守护线程关闭才会退出 
  65.         System.exit(0); 
  66.     } 

运行结果如下:

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

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

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

  1. kill -15 pid 

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

  1. 关闭 socket 链接
  2. 清理临时文件
  3. 发送消息通知给订阅方,告知自己下线
  4. 将自己将要被销毁的消息通知给子进程
  5. 各种资源的释放
  6. ...

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

  1. 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运行了死循环代码。

【编辑推荐】

  1. 挑逗Java程序员的那些Scala绝技
  2. Java: 未来已来 这些炫酷特性你确定不看吗?
  3. Java多线程编程 — 锁优化
  4. 如何准备Java初级和高级的技术面试
  5. 用350行代码从零开始,将Lisp编译成JavaScript
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Visual C# 2005从入门到精通

Microsoft Visual C#功能强大、使用简单。本书全面介绍了如何利用Visual Studio2005和NET Framework来进行C#编程。作者将C#的各种特性娓娓...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊