上篇文章 ShutdownHook- Java 优雅停机解决方案 提到应用停机时须要释放资源,关闭链接。对于一些定时任务或者网络请求服务将会使用线程池,当应用停机时须要正确安全的关闭线程池,若是处理不当,可能形成数据丢失,业务请求结果不正确等问题。html
关闭线程池咱们能够选择什么都不作,JVM 关闭时天然的会清除线程池对象。固然这么作,存在很大的弊端,线程池中正在执行执行的线程以及队列中还未执行任务将会变得极不可控。因此咱们须要想办法控制到这些未执行的任务以及正在执行的线程。java
线程池 API 提供两个主动关闭的方法 ThreadPoolExecutor#shutdownNow
与 ThreadPoolExecutor#shutdown
,这两个方法均可以用于关闭线程池,可是具体效果却不太同样。安全
在说线程池关闭方法以前,咱们先了解线程池状态。网络
线程池状态关系图以下:ide
从上图咱们看到线程池总共存在 5 种状态,分别为:函数
terminated()
钩子方法。terminated()
钩子方法以后。当咱们执行 ThreadPoolExecutor#shutdown
方法将会使线程池状态从 RUNNING 转变为 SHUTDOWN。而调用 ThreadPoolExecutor#shutdownNow
以后线程池状态将会从 RUNNING 转变为 STOP。从上面的图上还能够看到,当线程池处于 SHUTDOWN,咱们仍是能够继续调用 ThreadPoolExecutor#shutdownNow
方法,将其状态转变为 STOP 。this
上面咱们知道线程池状态,这里先说说 shutdown
方法。shutdown
方法源码比较简单,能比较直观理解其调用逻辑。idea
shutdown
方法源码:spa
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 检查权限 checkShutdownAccess(); // 设置线程池状态 advanceRunState(SHUTDOWN); // 中断空闲线程 interruptIdleWorkers(); // 钩子函数,主要用于清理一些资源 onShutdown(); } finally { mainLock.unlock(); } tryTerminate(); }
shutdown
方法首先加锁,其次先检查系统安装状态。接着就会将线程池状态变为 SHUTDOWN,在这以后线程池再也不接受提交的新任务。此时若是还继续往线程池提交任务,将会使用线程池拒绝策略响应,默认状况下将会使用 ThreadPoolExecutor.AbortPolicy
,抛出 RejectedExecutionException
异常。线程
interruptIdleWorkers
方法只会中断空闲的线程,不会中断正在执行任务的的线程。空闲的线程将会阻塞在线程池的阻塞队列上。
线程池构造参数须要指定 coreSize(核心线程池数量),maximumPoolSize(最大的线程池数量),keepAliveTime(多余空闲线程等待时间),unit(时间单位),workQueue(阻塞队列)。
当调用线程池的 execute
方法,线程池工做流程以下:
工做流程图以下:
当线程池处于第二步时,线程将会使用 workQueue#take
获取队头的任务,而后完成任务。若是工做队列一直没任务,因为队列为阻塞队列,workQueue#take
将会阻塞线程。
ThreadPoolExecutor#shutdownNow
源码以下:
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 检查状态 checkShutdownAccess(); // 将线程池状态变为 STOP advanceRunState(STOP); // 中断全部线程,包括工做线程以及空闲线程 interruptWorkers(); // 丢弃工做队列中存量任务 tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
shutdownNow
方法将会把线程池状态设置为 STOP,而后中断全部线程,最后取出工做队列中全部未完成的任务返回给调用者。
对比 shutdown
方法,shutdownNow
方法比较粗暴,直接中断工做线程。不过这里须要注意,中断线程并不表明线程马上结束。这里须要线程主动配合线程中断响应。
线程中断机制:
thread#interrupt
只是设置一个中断标志,不会当即中断正常的线程。若是想让中断当即生效,必须在线程 内调用Thread.interrupted()
判断线程的中断状态。
对于阻塞的线程,调用中断时,线程将会马上退出阻塞状态并抛出InterruptedException
异常。因此对于阻塞线程须要正确处理InterruptedException
异常。
线程池 shutdown
与 shutdownNow
方法都不会主动等待执行任务的结束,若是须要等到线程池任务执行结束,须要调用 awaitTermination
主动等待任务调用结束。
调用方法以下:
threadPool.shutdown(); try { while (!threadPool.awaitTermination(60,TimeUnit.SECONDS)){ System.out.println("线程池任务还未执行结束"); } } catch (InterruptedException e) { e.printStackTrace(); }
若是线程池任务执行结束,awaitTermination
方法将会返回 true
,不然当等待时间超过指定时间后将会返回 false
。
若是须要使用这种进制,建议在上面的基础上增长必定重试次数。这个真的很重要!!!
回顾上面线程池状态关系图,咱们能够知道处于 SHUTDOWN 的状态下的线程池依旧能够调用 shutdownNow
。因此咱们能够结合 shutdown
, shutdownNow
,awaitTermination
,更加优雅关闭线程池。
threadPool.shutdown(); // Disable new tasks from being submitted // 设定最大重试次数 try { // 等待 60 s if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { // 调用 shutdownNow 取消正在执行的任务 threadPool.shutdownNow(); // 再次等待 60 s,若是还未结束,能够再次尝试,或则直接放弃 if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("线程池任务未正常执行结束"); } } catch (InterruptedException ie) { // 从新调用 shutdownNow threadPool.shutdownNow(); }
文章首发于 studyidea.cn/close..
欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客: studyidea.cn