Redian新闻
>
原来,这才是 JDK 推荐的线程关闭方式

原来,这才是 JDK 推荐的线程关闭方式

公众号新闻

点击上方Java后端编程”,选择“设为星标

优质文章,及时送达


【干货】ChatGPT 4.0 解锁了,不限制次数提问 !!!
【干货】清华学姐自学的Linux笔记,天花板级别!
【干货】国内直接使用,国产之光,低调使用 !
【干货】ChatGPT中文版 4.0,人人都能用,快速稳定 !

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.InterruptibleChannel,Channel 将被关闭,线程的中断位被设置,同时目标线程收到 java.nio.channels.ClosedByInterruptException。

  • 如果目标线程被阻塞在 java.nio.channels.Selector,线程中断状态被设置,然后目标线程立即从 select 中返回非零值。

  • 如果其他条件都不成立,该线程中断位会被设置。


线程中断位标记了当前线程是否处于被中断状态,并且提供了 Thread.isInterrupted 方法查看当前是否处于中断位?那为什么目标线程阻塞在 Object.wait(),Sleep() 方法时,抛出了 interruptException,会取消标记呢?实际上 interrupt 操作执行两件事,1)设置中断位标记 2)通过 unpark 唤醒目标线程(park 和 unpark 分别可以阻塞线程和唤醒线程)


然而目标线程醒来时会检查当前是否处于中断位,如果是 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 处理中断也可以优雅退出,但需要上下层堆栈都要关注中断,不得吞掉中断。


来源:五阳神功,

链接:juejin.cn/post/7291564831710445622

—  —


 ChatGPT中文网站



可以在国内同ChatGPT直接进行对话,支持语音聊天,GPT4.0 和 AI绘图,简直太方便了,今天新注册的直接送4.0提问次数 !


https://aigc.cxyquan.com

   直接扫码体验



微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
过泗洪双沟玩疯了,坐地中海游轮乐极生悲和有惊无险求求你们别再用 kill -9 了,这才是 Spring Boot 停机的正确方式!!!重要!麻省理工官方推荐的26个竞赛和29个夏校,看看有没有适合你的...Java 近期新闻:JDK 22 的 JEP、Spring Shell、Quarkus、Apache Camel、JDKMon原来这才是玩遍纽约的最好方式!圣诞季/新年出游,没ta不行!瑞·达利欧(Ray Dalio)推荐的25本最佳非虚构类书籍看了美国小学推荐的书单,每本都值得多读几遍直飞只需3小时!《国家地理》推荐的拥有世界最细沙滩和最美日落的海岛,是时候启程了!均价 ¥100+,这才是极简护肤的正确打开方式无高球不高兴【收藏】这是纽约华人推荐的51家正宗中餐馆天涯第29次推荐的洗面奶!人手一支, 黑头脏东西说byebye~作为一个青岛人,这是我最愿意推荐的新年礼物跟着公交车漫游,去小红书不推荐的犄角旮旯|席外话 Vol.09还在刷《投行400问》?这本哈佛推荐的171页面试指南更高效阿宝强烈推荐的「十肽霜」,30天内不满意不要钱!家政阿姨盛情推荐的4款清洁好物,助你旧貌大换新!“这才是结束一段感情的正确方式”下乡时吹过的竹笛Spring Framework 6.1正式GA,兼容虚拟线程和JDK 21洛杉矶11月新店|北美版茶颜悦色, 周董推荐的奶茶, 川菜火锅串串..馋晕!纽森扫大街,迎接爱拍客(APEC)首席AI教育者Sebastian Raschka博士推荐的2023年最具影响力的 10 篇AI论文近期唯一推荐的好书,连刷3遍都不过瘾舌尖上的中国总顾问推荐的零食,一众明星都在吃!2024新年礼盒来了,好吃好看,亲友收到都说好!这才是2024正确的打开方式!366天遇见唯美敦煌,难得一见的海外遗珍,太惊艳!学会看JD有多重要?JD看明白了面试机会就多了一梳白发变黑发!这把明星推荐的「懒人染发梳」,省时省力,染完年轻10岁不止!难怪减肥难!《自然》子刊:肥胖让你的线粒体碎成渣,加速脂肪堆积;近5万人研究发现:常吃这类食物的女性,更健康长寿|本周论文推荐我第3次推荐的洁面!建议人手一支,冬天用好舒服~罗翔教授鼎力推荐的法律启蒙读物!采取全新体裁,选取69篇常见案例,帮助孩子知其然、更知其所以然我体验了GPT Store 推荐的首批 GPTs,发现 app 们还失不了业Redis7 单线程VS多线程头等舱御用零食,泰国爆火还魂梅登陆北美!一口酸爽难忘,明星推荐的宝藏零食,五星级酒店也有它!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。