如何优雅的关闭线程池?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何优雅的关闭线程池?相关的知识,希望对你有一定的参考价值。

参考技术A 线程池是系统资源,这篇文章主要介绍如何优雅关闭线程池

相关API:

Runtime.addShutdownHook解释

如果你想在jvm关闭的时候进行内存清理、对象销毁等操作,或者仅仅想起个线程然后这个线程不会退出,你可以使用Runtime.addShutdownHook。

这个方法的作用就是在JVM中增加一个关闭的钩子。当程序正常退出、系统调用 System.exit方法或者虚拟机被关闭时才会执行系统中已经设置的所有钩子,当系统执行完这些钩子后,JVM才会关闭。所谓钩子,就是一个已初始化但并不启动的线程。JVM退出通常通过两种事件。

程序正常退出,例如最后一个非守护进程退出、使用System.exit()退出等

程序异常退出,例如使用Ctrl+C触发的中断、用户退出或系统关闭等系统事件等 , 详情见官方文档:https://docs.oracle.com/javase/8/docs/api/index.html

Guava解释

google退出的open sdk,提供多类并发api。

上两篇文章讲了@Configuration @Bean @Import注入线程池Bean,还有ApplicationRunner 和 CommandLineRunner接口去实现容器启动完成事件驱动,所以结合起来举个例子,在项目中如何注入、优雅关闭线程池。

如上图。

1.通过@Configuration @Bean注解去注入一个线程池<componentThreadPool>。

2.将线程池注册到注册中心<ThreadPoolRegistrationCenter>

3.在Spring容器启动完成观察者模式中,利用ApplicationRunner接口提供的run方法,添加jvm hook钩子,以做到jvm退出时能够优雅关闭线程池。

其中用到了guava的<MoreExecutors.shutdownAndAwaitTermination>,jdk<Runtime.getRuntime().addShutdownHook>等API

优雅关闭线程池

前言

这一类的文章在公众号其实是很多的,所以这一篇主要是加深自己对线程池的相关函数以及关闭线程池的知识点进行总结~

why?

为啥会出现如何优雅关闭线程池这类问题?比如说线程池还在执行任务,这时jvm就关闭了,很多任务是直接out对吧。所以说如何解决这个问题。

how?

RunTime.getRunTime().addShutdownHook

新增一个关闭回调钩子,就是在jvm关闭的时候会进行回调。

其次,如果是你去完成关闭线程池的功能你会怎么设计呢

PS:之前面阿里的时候,有个小组leader说我还有一些细节的东西没有了解全面,但是…很多东西原理都是互通的,比如说网关,nginx转发等等,跟gateway还有dubbo转发原理是一样的,正所谓大道至简~

那我们设计的话:

  1. 超时关闭,这个等待时间是没有人知道的,像接口超时有些固定5秒,10秒,20秒
  2. 会记录下丢失的任务线程id
  3. 打印相关的日志

相关参数

这里以ExecutorService为例

shutdowm

在这里插入图片描述
启动有序关闭,其中执行先前提交的任务,但不会接受新任务。如果已经关闭,调用没有额外的效果。 此方法不等待先前提交的任务完成执行。使用 awaitTermination 来做到这一点。

shutdownNow
在这里插入图片描述
尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。 此方法不会等待主动执行的任务终止。使用 awaitTermination 来做到这一点。 除了尽力尝试停止处理正在执行的任务之外,没有任何保证。例如,典型的实现将通过 Thread.interrupt() 取消,因此任何未能响应中断的任务可能永远不会终止。 返回: 从未开始执行的任务列表

所以这个方法是可以返回未开始执行的任务列表,也就是打印相关还未执行的线程。

shutdown跟shutdownNow区别

shutdownNow会主动去尝试终止正在进行的任务,而shutdown只是不再接受新的任务

具体关闭线程池代码

The following method shuts down an ExecutorService in two phases, first by calling shutdown to reject incoming tasks, and then calling shutdownNow, if necessary, to cancel any lingering tasks:
 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }
 

当然这只是个demo,如果你觉得像接口重试,或者连接超时那样,只要超过特定时间就抛出异常,也可以,等待完直接shutdownNow,会有点暴力。

参考资料

以上是关于如何优雅的关闭线程池?的主要内容,如果未能解决你的问题,请参考以下文章

优雅关闭线程池

如何优雅地关闭线程池?从源码剖析线程池的正确销毁姿势。

Java基础干货如何优雅关闭线程池实践总结

如果优雅地关闭ExecutorService提供的java线程池

深入浅出多线程编程实战优雅关闭线程池

深入浅出多线程编程实战优雅关闭线程池