0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

JDK如何优雅退出一个线程?

马哥Linux运维 来源:cnblogs 2023-11-17 10:02 次阅读

JDK 在线程的 Stop 方法时明确不得强行销毁一个线程,要优雅的退出线程。

何谓优雅退出线程,即业务将进行中请求正确被处理,取消待执行请求,执行资源回收,最终Thread Runable run方法 return 结束执行。

首先问为什么要退出一个线程,再提问如何退出一个线程

1需要线程退出的常见场景

任务执行完成,或异常终止,任务认为无需再占用线程。

线程池根据当前任务执行情况,伸缩线程池。当任务执行较少时,退出空闲的线程。

服务或进程在关闭阶段,例如滚动发布时,需要退出线程、关闭线程池、关闭进程。

定时任务、周期任务需要终止执行时,需要退出当前线程。或者退出当前任务的执行。

总之既然能创建一个线程,就会有退出一个线程的能力。也会有退出线程的场景。

关闭一个线程的方式分为两种类型:通知线程主动关闭和强行关闭销毁线程。

2优雅关闭 or 强行关闭

c05da122-84e5-11ee-939d-92fbcf53809c.png

实际上强行关闭一个线程,坏处很多,假如要释放分布式锁前,突然关闭线程,那么这个分布式锁就无法释放。导致后续正常请求加锁失败被阻塞,影响用户提单等。强行关闭一个线程无异于给服务器直接断电。

3其他语言和 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 中断方式通知目标线程。

下面我们探讨下如何优雅退出一个线程?

4优雅退出线程

有哪些方式呢?

业务字段标记

业务系统经常遇到终止一个任务的诉求,例如系统中存在定时任务,例如外卖券包在过期后,未使用的金额,自动给用户退款。假设任务执行中,我需要重新制定任务的入参,需要先终止任务。如何做呢?

大部分任务类代码都会循环处理,例如扫描全表执行某个业务逻辑。一定存在循环处理的场景,可以在循环入口处判断任务是否需要终止执行,这样通过控制这个字段,我们就可以终止任务执行。

具体实施时,可以通过配置中心控制某一个任务是否要终止。

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(inti=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()。

5总结

不推荐强制销毁线程,会导致资源无法被释放,进行中请求无法正常处理完,导致业务数据处于不可知的状态。

Java 推荐优雅退出线程。

业务层可以使用字段标记,定期检查是否需要退出任务。

Thread.interrupt中断目标线程、isInterrupted查询中断位标记。

使用Thread.interrupt处理中断也可以优雅退出,但需要上下层堆栈都要关注中断,不得吞掉中断。

编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2966

    浏览量

    104701
  • C语言
    +关注

    关注

    180

    文章

    7604

    浏览量

    136684
  • C++
    C++
    +关注

    关注

    22

    文章

    2108

    浏览量

    73618
  • JDK
    JDK
    +关注

    关注

    0

    文章

    81

    浏览量

    16592
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19675

原文标题:JDK 推荐的线程关闭方式

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【RT-Thread学习笔记】如何优雅退出QEMU模拟器?

    【RT-Thread学习笔记】如何优雅退出QEMU模拟器?我想重新编译源码,再次运行新的代码,怎么办呢?如何才能退出这个QEMU命令行控制台?
    的头像 发表于 07-26 04:06 5940次阅读
    【RT-Thread学习笔记】如何<b class='flag-5'>优雅</b>地<b class='flag-5'>退出</b>QEMU模拟器?

    RTThread中main线程循环,如果main线程异常退出了,有什么办法可以监测到?

    RTThread中main线程循环,如果main线程异常退出了,有什么办法可以监测到?
    发表于 02-22 08:15

    线程退出机制初探

    ,使得前两循环也结束了。从而达到按“退出”键的目的。为了加深理解,可以做另一个测试。将其中循环的内部延时加长,如20秒。点击“
    发表于 02-13 20:13

    LWIP相关的线程是如何通过信号控制创建的呢

    。但该功能的实现依赖LWIP创建的几个线程:etx/erx/tcip/modbustcptest,如果优雅稳定可靠地的通过开关信号(按键,菜单启停)做到这点?
    发表于 07-13 10:21

    教你种如何优雅退出QEMU模拟器的方法

    1、如何优雅退出QEMU模拟器大家都知道,Linux退出控制台启动的程序,使用CTRL+C就可以把它
    发表于 08-26 16:12

    java jdk6.0官方下载

    java jdk6.0下载如何件: java jdk6.0安装步骤: 第步 JDK1.6的安装步骤 第步双击安装文件
    发表于 10-17 11:47 155次下载
    java <b class='flag-5'>jdk</b>6.0官方下载

    线程编程之Linux线程编程

    的可移植性。 (1)函数说明。 创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建以后,就开始运行相关的线程函数,在该函数运
    发表于 10-18 15:55 3次下载

    JDK 19 / Java 19正式发布 虚拟线程来了

    记录模式 (预览版) Linux/RISC-V 移植 外部函数和内存 API (预览版) 虚拟线程(预览版) Vector API (第四次孵化) Switch 模式匹配(第三预览版) 结构化并发(孵化阶段) JDK 19 / J
    的头像 发表于 10-10 17:08 1938次阅读

    细数线程池的10

    JDK开发者提供了线程池的实现类,我们基于Executors组件,就可以快速创建线程池 。
    的头像 发表于 06-16 10:11 722次阅读
    细数<b class='flag-5'>线程</b>池的10<b class='flag-5'>个</b>坑

    线程池的两思考

    今天还是说一下线程池的两思考。 池子 我们常用的线程池, JDK的ThreadPoolExecutor. CompletableFutures 默认使用了
    的头像 发表于 09-30 11:21 3100次阅读
    <b class='flag-5'>线程</b>池的两<b class='flag-5'>个</b>思考

    如何使用JDK截断一个字符串

    在本文中,我们将学习在Java中把String截断到所需的字符数的集中方法。 首先,我们将探索使用JDK本身来实现这目标的方法。然后,我们将研究如何使用
    的头像 发表于 10-08 15:43 515次阅读

    JDK中常见的Lamada表达式

    JDK中有许多函数式接口,也会有许多方法会使用函数式接口作为参数,同时在各种源码中也大量使用了这些方法,那么我们在实际工作中应该如何使用!我们就来盘盘,这样也有助于写出优雅的代码,使我们在阅读源码
    的头像 发表于 10-10 15:07 515次阅读
    <b class='flag-5'>JDK</b>中常见的Lamada表达式

    如何查看线程的ID

    1.什么是线程? linux内核中是没有线程这个概念的,而是轻量级进程的概念:LWP。般我们所说的线程概念是C库当中的概念。 1.1线程
    的头像 发表于 11-13 14:38 1373次阅读
    如何查看<b class='flag-5'>一</b><b class='flag-5'>个</b><b class='flag-5'>线程</b>的ID

    weblogic修改jdk路径

    WebLogic是流行的Java应用服务器,可以用于部署和管理企业级Java应用程序。在WebLogic的安装和配置过程中,我们可能会遇到需要修改JDK(Java Development Kit
    的头像 发表于 12-05 14:46 1299次阅读

    如何配置jdk的环境变量

    /javase-jdk11-downloads.html)选择适合您操作系统的JDK版本,并下载它。 第二步:安装JDK 下载完成后,运行JDK的安装程序,并按照提示进行安装。选择
    的头像 发表于 12-06 15:07 835次阅读