java如何关闭线程池_如何优雅的关闭Java线程池
⾯试中经常会问到,创建⼀个线程池需要哪些参数、线程池的工作原理,却很少会问到线程池如何安全关闭的。
也正是因为⼤家不是很关注这块,即便是⼯作三四年的⼈,也会有因为线程池关闭不合理,导致应用⽆法正常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线程池相关推荐
- java 关闭阻塞线程池_如果优雅地关闭ExecutorService提供的java线程池
每一个线程都会占用系统资源,因此线程池的关闭与清理同样重要,本文介绍我们如何优雅地关闭线程池. 一. ExecutorService中关闭线程池的方法 1. shutdown() 停止接收新任务,原来 ...
- 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析
文章目录 概述 jstack或者可视化工具检测是否死锁(没有) 原因分析 概述 高并发编程-线程通信_使用wait和notify进行线程间的通信 - 遗留问题 我们看到了 应用卡住了 .... 怀疑是 ...
- JAVA线程池_并发队列工作笔记0003---线程池的分类_可缓存线程池_定长线程池_定时线程池_单例线程池
技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这里说线程池的分类 有可缓存类型, 定长类型, 定时类型, 单例类型, 这里我这次用Executo ...
- 项目使用线程池_并发编程系列1:线程池的架构实现、大小配置、及四种线程池使用...
△ 公众号回复关键词"架构" 即可领取<1500+BAT架构及面试专题合集> 本篇为线程池系列文章之一,不经常使用线程池的童鞋,还有对几种线程的使用不甚了解的童鞋,可以 ...
- vmx进程已提前退出_如何优雅地停止Java进程
目录 理解停止Java进程的本质 应该如何正确地停止Java进程如何注册关闭钩子使用关闭钩子的注意事项信号量机制 总结 理解停止Java进程的本质 我们知道,Java程序的运行需要一个运行时环境,即: ...
- java 句柄池_深入理解JVM之Java对象的创建、内存布局、访问定位详解
本文实例讲述了深入理解JVM之Java对象的创建.内存布局.访问定位.分享给大家供大家参考,具体如下: 对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含 ...
- nginx没有worker进程_如何优雅地关闭worker进程?
点击上方"武培轩",选择"设为星标" 技术文章第一时间送达! 之前我们讲解 Nginx 命令行的时候,可以看到 Nginx 停止有两种方式,分别是 nginx ...
- 线程销毁_多线程(2)-Java高级知识(9)
前面有一篇文章写的是多线程的基本知识,以及线程实现的两种方式,一种是继承Thread类,另一种实现Runnable 接口,今天沿着前面的多线程,继续分享多线程的第三种创建方法,以及第三种创建方法的优势 ...
- 使用线程锁(lock)实现线程同步_一文搞懂Java多线程使用方式、实现原理以及常见面试题...
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
最新文章
- (转载)为什么欧美拿金牌不感谢祖国
- c++中类的构造函数的初始化成员列表详解
- 子组件获取父组件的值,将这个值作为状态值保存
- Java高并发系统的限流策略
- JAVA程序设计----练习题
- 订单编号,递增且不连续(php版)
- python避坑指南_Linux下Python3.6的安装及避坑指南
- 【论文学习】Bringing Old Photos Back to Life
- win10收集错误信息重启蓝屏解决
- 使用可靠多播与OPENDDS进行数据分发
- oracle mysql 推荐书籍
- jquery点击按钮删除文本框的内容
- 华硕笔记本Delete键和Insert键合二为一与分离
- IDEA中Maven中mysql-connector-java或者其他依赖包下载不了的问题的解决办法
- NB-IoT和GSM概述
- cmd 测试服务器通过延迟,网络延迟测试_怎么测试网络延迟?
- iphone 信号对应设备_如何访问iPhone的现场测试模式(并查看实际信号强度)
- sqlserver:关于timestamp时间戳 rowversion
- Mac OS X下的动态链接库
- 消息重放攻击以及预防方法