⾯试中经常会问到,创建⼀个线程池需要哪些参数、线程池的工作原理,却很少会问到线程池如何安全关闭的。

也正是因为⼤家不是很关注这块,即便是⼯作三四年的⼈,也会有因为线程池关闭不合理,导致应用⽆法正常stop的情况,还有出现⼀些报错的问题。

本篇就以ThreadPoolExecutor为例例,来介绍下如何优雅的关闭线程池。

线程中断

在介绍线程池关闭之前,先介绍下Thread的interrupt。

在程序中,我们是不能随便中断⼀个线程的,因为这是极其不安全的操作,我们⽆法知道这个线程正运⾏在什么状态,它可能持有某把锁,强⾏中断可能导致锁不能释放的问题;或者线程可能在操作数据库,强⾏中断导致数据不一致,从而混乱的问题。正因此,Java⾥将Thread的stop⽅法设置为过时,以禁⽌⼤家使⽤。

⼀个线程什么时候可以退出呢?当然只有线程自⼰才能知道。

所以我们这⾥要说的Thread的interrrupt⽅法,本质不是⽤来中断一个线程。而是将线程设置⼀个中断状态。当我们调⽤线程的interrupt方法,它有两个作⽤用:

1、如果此线程处于阻塞状态(比如调⽤了wait方法,io等待),则会立刻退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做⼀定的处理,然后让线程退出。

2、如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自⼰是否被中断,并做退出操作。

注:如果线程的interrupt方法先被调用,然后线程调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。如果线程捕获InterruptedException异常后,继续调用阻塞方法, 将不再触发InterruptedException异常。

线程池的关闭

线程池提供了两个关闭方法,shutdownNow和shuwdown⽅法。

shutdownNow⽅法的解释是:线程池拒接收新提交的任务,同时⽴刻关闭线程池,线程池里的任务不再执行。

shutdown⽅法的解释是:线程池拒接收新提交的任务,同时等待线程池⾥的任务执行完毕后关闭线程池。

以上的说法虽然没错,但是还有很多的细节问题,比如调用shutdown⽅法后,正在执⾏的任务的线程会做出什么反应?正在等待任务的线程又会做出什么反应?线程在什么情况下才会彻底退出。如果不了解这些细节,在关闭线程池时就难免遇到,像线程池关闭不了,关闭线程池出现报错等情况。

再说这些关闭线程池细节问题之前,需要强调一点的是,调用完shutdownNow和shuwdown⽅法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调⽤awaitTermination⽅法进⾏同步等待。

有了以上介绍,下⾯就结合线程池源码,分别说说这两个线程池关闭方法的⼀一些实现细节。

shutdownNow

我们看⼀下shutdownNow⽅法的源码:

在shutdownNow⽅法里,重要的三句代码我⽤红⾊数字标出来了。第⼀句就是原⼦性的修改线程池的状态为STOP状态(比较简单,我就不贴代码了) ,第三句是将队列里还没有执⾏的任务放到列表里,返回给调用方,第⼆句是遍历线程池⾥的所有⼯作线程,然后调用线程的interrupt方法。如下图:

以上就是shutdownNow⽅法的执⾏逻辑:将线程池状态修改为STOP,然后调⽤线程池⾥的所有线程的interrupt⽅法。

调⽤shutdownNow后,线程池⾥的线程会做如何反应呢?那就要看,线程池⾥的线程正在执⾏的代码逻辑了。其在线程池的runWorker⽅法里(对线程池的执行原理不了解的,请看之前的文章),其代码如下:

正常情况下,线程池里的线程,就是在这个while循环里不停地执行。其中代码task.run()就是在执⾏我们提交给线程池的任务,如当我们调用shutdownNow 时,task.run()⾥⾯正处于IO阻塞,则会导致报错,如果task.run()⾥正在正常执

⾏,则不受影响,继续执⾏完这个任务。

从上图看得出来,如果getTask()⽅法返回null,也会导致线程的退出。我们再来看看getTask⽅法的实现:

如果我们调⽤shutdownNow⽅法时,线程处于从队列⾥读取任务⽽阻塞中(图中下边的红框),则会导致抛出InterruptedException异常,但因为异常被捕获,线程将会继续在这个for循环⾥执⾏。

还记得shutdownNow⽅法里将线程修改为STOP状态吧,当执行到上边红框⾥的代码时,由于STOP状态值是⼤于SHUTDOWN状态,STOP也大于等于STOP,不管任务队列是否为空,都会进⼊if语句从而返回null,线程退出。

总结:当我们调用线程池的shutdownNow时,如果线程正在getTask方法中执⾏,则会通过for循环进入到if语句,于是getTask 返回null,从而线程退出。不管线程池⾥是否有未完成的任务。如果线程因为执行提交到线程池里的任务而处于阻塞状态,则会导致报错。(如果任务里没有捕获InterruptedException异常),否则线程会执行完当前任务,然后通过getTask方法返回为null来退出。

shutdown

我们再来看看shutdown⽅法的源码:

跟shutdownNow类似,只不过它是将线程池的状态修改为SHUTDOWN状态,然后调⽤interruptIdleWorkers方法,来中断空闲的线程。这是interruptIdleWorkers⽅法的实现:

跟shutdownNow⽅法调⽤interruptWorkers⽅法不同的是,interruptIdleWorkers⽅法在遍历线程池⾥的线程时,有一个w.tryLock()加锁判断,只有加锁成功的线程才会被调用interrupt方法。那么什么情况下才能被加锁成功?什么情况下不能被加锁成功呢?这就需要我们继续回到线程执行的runWorker方法。

在上边runWorker方法代码的截图中,我刻意将w.lock()和w.unlock()调用用红框 圈起。其实就是正运行在w.lock和w.unlock之间的线程将因为加锁失败,而不会被调用interrupt方法,换句话说,就是正在执行线程池里任务的线程不会被中断。

不管是被调用了interrupt的线程还是没被调用的线程,什么时候退出呢?,这就要看getTask⽅法的返回是否为null了。

在getTask里的if判断(上文中getTask代码截图中上边红色方框的代码)中,由于线程池被shutdown⽅法修改为SHUTDOWN状态,SHUTDOWN大于等于

SHUTDOWN成⽴没问题,但是SHUTDOWN不⼤于等于STOP状态,所以只有队列为空,getTask方法才会返回null,导致线程退出。

总结:当我们调用线程池的shuwdown方法时,如果线程正在执行线程池里的任务,即便任务处于阻塞状态,线程也不会被中断,⽽是继续执行。如果线程池阻塞等待从队列⾥读取任务,则会被唤醒,但是会继续判断队列是否为空,若不为空,则会继续从队列里读取任务,若为空则线程退出。

优雅的关闭线程池

有了上边对两个关闭线程池方法的了解,相信优雅安全关闭线程池将不再是问题。

我们知道,使⽤shutdownNow⽅法,可能会引起报错,使用shutdown方法可能会导致线程关闭不了。

所以当我们使⽤shutdownNow⽅法关闭线程池时,一定要对任务里进行异常捕获。

当我们使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。

最后,⼀定要记得shutdownNow和shuwdown调用完,线程池并不是⽴刻就关闭了,要想等待线程池关闭,还需调用awaitTermination⽅法来阻塞等待。

END

欢迎工作一到五年的Java程序员朋友们加入Java架构开发:744677563

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

java如何关闭线程池_如何优雅的关闭Java线程池相关推荐

  1. java 关闭阻塞线程池_如果优雅地关闭ExecutorService提供的java线程池

    每一个线程都会占用系统资源,因此线程池的关闭与清理同样重要,本文介绍我们如何优雅地关闭线程池. 一. ExecutorService中关闭线程池的方法 1. shutdown() 停止接收新任务,原来 ...

  2. 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析

    文章目录 概述 jstack或者可视化工具检测是否死锁(没有) 原因分析 概述 高并发编程-线程通信_使用wait和notify进行线程间的通信 - 遗留问题 我们看到了 应用卡住了 .... 怀疑是 ...

  3. JAVA线程池_并发队列工作笔记0003---线程池的分类_可缓存线程池_定长线程池_定时线程池_单例线程池

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这里说线程池的分类 有可缓存类型, 定长类型, 定时类型, 单例类型, 这里我这次用Executo ...

  4. 项目使用线程池_并发编程系列1:线程池的架构实现、大小配置、及四种线程池使用...

    △ 公众号回复关键词"架构" 即可领取<1500+BAT架构及面试专题合集> 本篇为线程池系列文章之一,不经常使用线程池的童鞋,还有对几种线程的使用不甚了解的童鞋,可以 ...

  5. vmx进程已提前退出_如何优雅地停止Java进程

    目录 理解停止Java进程的本质 应该如何正确地停止Java进程如何注册关闭钩子使用关闭钩子的注意事项信号量机制 总结 理解停止Java进程的本质 我们知道,Java程序的运行需要一个运行时环境,即: ...

  6. java 句柄池_深入理解JVM之Java对象的创建、内存布局、访问定位详解

    本文实例讲述了深入理解JVM之Java对象的创建.内存布局.访问定位.分享给大家供大家参考,具体如下: 对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含 ...

  7. nginx没有worker进程_如何优雅地关闭worker进程?

    点击上方"武培轩",选择"设为星标" 技术文章第一时间送达! 之前我们讲解 Nginx 命令行的时候,可以看到 Nginx 停止有两种方式,分别是 nginx ...

  8. 线程销毁_多线程(2)-Java高级知识(9)

    前面有一篇文章写的是多线程的基本知识,以及线程实现的两种方式,一种是继承Thread类,另一种实现Runnable 接口,今天沿着前面的多线程,继续分享多线程的第三种创建方法,以及第三种创建方法的优势 ...

  9. 使用线程锁(lock)实现线程同步_一文搞懂Java多线程使用方式、实现原理以及常见面试题...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. (转载)为什么欧美拿金牌不感谢祖国
  2. c++中类的构造函数的初始化成员列表详解
  3. 子组件获取父组件的值,将这个值作为状态值保存
  4. Java高并发系统的限流策略
  5. JAVA程序设计----练习题
  6. 订单编号,递增且不连续(php版)
  7. python避坑指南_Linux下Python3.6的安装及避坑指南
  8. 【论文学习】Bringing Old Photos Back to Life
  9. win10收集错误信息重启蓝屏解决
  10. 使用可靠多播与OPENDDS进行数据分发
  11. oracle mysql 推荐书籍
  12. jquery点击按钮删除文本框的内容
  13. 华硕笔记本Delete键和Insert键合二为一与分离
  14. IDEA中Maven中mysql-connector-java或者其他依赖包下载不了的问题的解决办法
  15. NB-IoT和GSM概述
  16. cmd 测试服务器通过延迟,网络延迟测试_怎么测试网络延迟?
  17. iphone 信号对应设备_如何访问iPhone的现场测试模式(并查看实际信号强度)
  18. sqlserver:关于timestamp时间戳 rowversion
  19. Mac OS X下的动态链接库
  20. 消息重放攻击以及预防方法

热门文章

  1. C++获取exe文件版本号
  2. Linux虚拟内存管理 | 虚拟地址与物理地址映射、段错误SIGSEGV
  3. C语言libcurl:RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议
  4. libcurl overview
  5. PHP导出成word文档
  6. TensorFlow 深入MNIST
  7. CUDA:在GPU上实现核函数的嵌套以及编译运行
  8. java语法优化小结
  9. android的蓝牙和数据库的开源项目地址
  10. php excel列增加_PHP 高性能 Excel 扩展 1.2.7 发布