工作 8 年才弄明白,原来,这才是JDK推荐的线程关闭方式
JDK在线程的Stop方法时明确不得强行销毁一个线程要优雅的退出线程。何谓优雅退出线程即业务将进行中请求正确被处理取消待执行请求执行资源回收最终Thread Runable run 方法return 结束执行。首先问为什么要退出一个线程再提问如何退出一个线程需要线程退出的常见场景任务执行完成或异常终止任务认为无需再占用线程。线程池根据当前任务执行情况伸缩线程池。当任务执行较少时退出空闲的线程。服务或进程在关闭阶段例如滚动发布时需要退出线程、关闭线程池、关闭进程。定时任务、周期任务需要终止执行时需要退出当前线程。或者退出当前任务的执行。总之既然能创建一个线程就会有退出一个线程的能力。也会有退出线程的场景。关闭一个线程的方式分为两种类型:通知线程主动关闭和强行关闭销毁线程。优雅关闭Or强行关闭标题好处坏处优雅关闭主动通知线程关闭能优雅退出线程保证资源被释放保证处理中请求正确被处理完成无法立即关闭线程执行中的任务不响应关闭信号拒绝关闭线程强行关闭线程可以立刻关闭线程存在资源未释放、处理中请求异常中断例如分布式锁漏释放。写流程异常中断数据不一致重试也无法恢复实际上强行关闭一个线程坏处很多假如要释放分布式锁前突然关闭线程那么这个分布式锁就无法释放。导致后续正常请求加锁失败被阻塞影响用户提单等。强行关闭一个线程无异于给服务器直接断电。其他语言和Java语言退出线程的方式除了Java其他语言如何退出线程呢实际上每一种实现方式都有。例如C中可以通过ExitThread、TerminateThread强行终止线程执行。linux既提供了pthread_exit C语言系统调用强行关闭线程也提供了pthread_cancel通知线程关闭等优雅退出方式。Java 也分别提供优雅和强制两种退出方式但是目前jdk中明确极不推荐强制中断线程在Thread.stop()强制中断线程的注释中 JDK这样解释Thread.stop() 这种方法本身就是不安全的Stop一个线程会随之解锁这个线程所持有的监视器可以理解为锁如果受这些监视器锁保护的临界对象处在不一致状态则其他线程可能会看到这些对象处于不一致状态那么将导致未知的行为。对Thread.Stop()的调用应该被简单的代码代替例如 修改一个变量目标线程定期检查这个变量有序从run 方法return出来。如果目标线程在一个条件变量上wait则其他线程应该使用interrupt方法中断目标线程。实际上关闭一个线程强行和通知是两种理念即是否应该相信线程任务的开发者优雅的、快速的主动退出线程而不是被其他线程强制终止。 在Java中退出线程的方式只有一种推荐即优雅退出并且jdk也给了建议通过修改变量由目标线程定期检查状态。或者通过interrupt中断方式通知目标线程。下面我们探讨下如何优雅退出一个线程优雅退出线程有哪些方式呢业务字段标记业务系统经常遇到终止一个任务的诉求例如系统中存在定时任务例如外卖券包在过期后未使用的金额自动给用户退款。假设任务执行中我需要重新制定任务的入参需要先终止任务。如何做呢大部分任务类代码都会循环处理例如扫描全表执行某个业务逻辑。一定存在循环处理的场景可以在循环入口处判断任务是否需要终止执行这样通过控制这个字段我们就可以终止任务执行。具体实施时可以通过配置中心控制某一个任务是否要终止。while(config.isTaskEnable()){//从配置中心获取任务是否要终止 //循环执行业务逻辑。直到执行完成退出或者被终止。 }这种退出方式是告知线程“你应该在合适时机退出” 由线程自己选择在合适的时机检查该状态。那么开发者在设计任务代码时就要提前设计 合理的退出点在退出点检查是否需要退出。Thread.interrupt()JDK中提到了如果目标线程没有处于运行态而是处于阻塞状态自然无法检查退出的状态标记如何通知这个线程退出呢JDK: 如果目标线程在一个条件变量上wait则其他线程应该使用interrupt方法中断目标线程。interrupt的JDK注释提到如果其他线程调用目标线程的interrupt方法恰好目标线程在调用. Object.wait()object.join(Object.sleep()等方法时目标线程的中断位标记被清除同时目标线程会立即从sleep、wait等调用中恢复并且被抛出InterruptException。如果目标线程在IO操作中被阻塞例如io.channels.InterruptibleChannelChannel将被关闭线程的中断位被设置同时目标线程收到java.nio.channels.ClosedByInterruptException。如果目标线程被阻塞在java.nio.channels.Selector线程中断状态被设置然后目标线程立即从select中返回非零值。如果其他条件都不成立该线程中断位会被设置。线程中断位标记了当前线程是否处于被中断状态并且提供了Thread.isInterrupted方法查看当前是否处于中断位那为什么目标线程阻塞在Object.wait()Sleep()方法时抛出了interruptException会取消标记呢实际上interrupt操作执行两件事1设置中断位标记 2通过unpark唤醒目标线程。park和unpark分别可以阻塞线程和唤醒线程推荐一个非常好的博客 通过JVM源码分析 interrupt和sleep的实现原理。# jvm源码分析之interrupt()然而目标线程醒来时会检查当前是否处于中断位如果是sleep或者wait操作。如果处于中断位则取消中断位抛出异常。取消中段位的原因应该是一种规范即抛出中断异常即通知了线程中断无需再用中段位标记。其他场景2、场景3 在被唤醒后分别执行对应的中断响应策略。interrupt中断逻辑是确定的业务线程要考虑自己是否调用了sleep、wait或者io、selector等操作根据不同的场景选择自己合适的中断响应策略。那么推荐业务线程如何响应中断呢推荐的中断响应策略立即响应中断目标线程的任务在InterruptedException异常处理中要主动回收资源打印日志退出任务执行。目标线程如果没有阻塞操作例如sleep、wait。可以通过 Thread.isInterrupted()查看当前中断位状态如果被中断了则采取以上第一步操作。忽略中断交给上一层处理所谓上一层可以理解为是调用堆栈的上一层例如本层代码不负责处理中断这个场景那么Interrupt异常被抛出后可以选择如何方案抛出InterruptedException给上层由上层代码处理。调用Thread.interrupt()。重新设置中断位标记(自己中断自己)。由上游代码在本层方法返回后检查中断位标记进行中断处理。当然最推荐的方式还是抛出InterruptedException让上游感知到下游调用链中存在阻塞让上游对中断异常进行处理。千万不要吞掉中断什么是吞掉中断例如当sleep抛出InterruptedException后忽略异常不执行任何操作继续执行业务逻辑。for (int i 0; i cnt; i) { try { //执行业务逻辑 Thread.sleep(10000); } catch (InterruptedException e) { System.out.println(被中断); } System.out.println(子线程执行中); }如果这样处理中断异常被忽略中断标记位也被忽略。即便上游方法对中断有处理策略也无法感知到中断。例如上游调用可能会判断while(true){ callChildMethod();//调用下游方法但是下游吞掉了中断 if (Thread.currentThread().isInterrupted()) { //回收资源退出线程 } }有人会问既然上层都能知道处理中断为什么下层方法开发者会不记得抛出中断或重置中断位呢因为上下两层很可能不是一个开发者。例如上层是通用的框架代码定义了任务的指定逻辑提供了扩展点方法下游只需要实现扩展方法即可。但是另一个开发者在实现扩展点方法时吞掉了中断异常导致本来框架层已经处理好中断了但还是无法响应中断。所以中断的响应是需要上下层每一层代码逻辑都需要考虑的事情。就算框架层处理好中断异常处理业务逻辑层也要关注中断处理。最后提醒一下Thread.interrupted方法会返回当前中断标记并且取消中断位。如果只查询中断位不想清理可以使用 Thread.isInterrupted()。总结不推荐强制销毁线程会导致资源无法被释放进行中请求无法正常处理完导致业务数据处于不可知的状态。Java推荐优雅退出线程。业务层可以使用字段标记定期检查是否需要退出任务。Thread.interrupt中断目标线程、isInterrupted查询中断位标记。使用Thread.interrupt处理中断也可以优雅退出但需要上下层堆栈都要关注中断不得吞掉中断。最后分享下五阳的浏览器AI插件没想到火了累计下载 1000同时提问豆包 DeepSeek GPT Gemini等10大 AI 平台还支持查询必应 百度 谷歌三大搜索引擎极大提升资料检索和学习效率领取链接
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2497970.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!