当前位置: 首页 > news >正文

Java——线程的中断

线程的中断

    • 1、取消/关闭的场景
    • 2、取消/关闭的机制
    • 3、线程对中断的反应
      • 3.1、Runnable
      • 3.2、Waiting/Timed_Waiting
      • 3.3、Blocked
      • 3.4、New/Terminate
    • 4、如何正确地取消/关闭线程

1、取消/关闭的场景

我们知道,通过线程的start方法启动一个线程后,线程开始执行run方法,run方法运行结束后线程退出,那为什么还需要结束一个线程呢?有多种情况,比如:

  1. 很多线程的运行模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停地从队列中接受任务,执行任务,在停止程序时,我们需要一种“优雅”的方法以关闭该线程。
  2. 在一些图形用户界面程序中,线程是用户启动的,完成一些任务,比如从远程服务器上下载一个文件,在下载过程中,用户可能会希望取消该任务。
  3. 在一些场景中,比如从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务。
  4. 有时,我们会启动多个线程做同一件事,比如类似抢火车票,我们可能会让多个好友帮忙从多个渠道买火车票,只要有一个渠道买到了,我们会通知取消其他渠道。

2、取消/关闭的机制

Java的Thread类定义了如下方法:

publicfinalvoidstop()

这个方法看上去就可以停止线程,但这个方法被标记为了过时,简单地说,我们不应该使用它,可以忽略它。

在Java中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。

Thread类定义了如下关于中断的方法:

publicbooleanisInterrupted()publicvoidinterrupt()publicstaticbooleaninterrupted()

这三个方法名字类似,比较容易混淆,我们解释一下。isInterrupted()和interrupt()是实例方法,调用它们需要通过线程对象;interrupted()是静态方法,实际会调用Thread. currentThread()操作当前线程。

每个线程都有一个标志位,表示该线程是否被中断了。

  1. isInterrupted:返回对应线程的中断标志位是否为true。
  2. interrupted:返回当前线程的中断标志位是否为true,但它还有一个重要的副作用,就是清空中断标志位,也就是说,连续两次调用interrupted(),第一次返回的结果为true,第二次一般就是false(除非同时又发生了一次中断)​。
  3. interrupt:表示中断对应的线程。中断具体意味着什么呢?下面我们进一步来说明。

3、线程对中断的反应

interrupt()对线程的影响与线程的状态和在进行的IO操作有关。我们主要考虑线程的状态,IO操作的影响和具体IO以及操作系统有关,我们就不讨论了。线程状态有:

  • RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度。
  • WAITING/TIMED_WAITING:线程在等待某个条件或超时。
  • BLOCKED:线程在等待锁,试图进入同步块。
  • NEW/TERMINATED:线程还未启动或已结束。

3.1、Runnable

如果线程在运行中,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,没有任何其他作用。线程应该在运行过程中合适的位置检查中断标志位,比如,如果主体代码是一个循环,可以在循环开始处进行检查,如下所示:

publicclassInterruptRunnableDemoextendsThread{@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){//…单次循环代码}System.out.println("done ");}//其他代码}

3.2、Waiting/Timed_Waiting

线程调用join/wait/sleep方法会进入WAITING或TIMED_WAITING状态,在这些状态时,对线程对象调用interrupt()会使得该线程抛出InterruptedException。需要注意的是,抛出异常后,中断标志位会被清空,而不是被设置。比如,执行如下代码:

Threadt=newThread(){@Overridepublicvoidrun(){try{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println(isInterrupted());}}};t.start();try{Thread.sleep(100);}catch(InterruptedExceptione){}t.interrupt();

程序的输出为false。

InterruptedException是一个受检异常,线程必须进行处理。我们在异常处理中介绍过,处理异常的基本思路是:如果知道怎么处理,就进行处理,如果不知道,就应该向上传递,通常情况下不应该捕获异常然后忽略。

捕获到InterruptedException,通常表示希望结束该线程,线程大致有两种处理方式:

  1. 向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理;
  2. 有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断。

第一种方式的示例代码如下:

publicvoidinterruptibleMethod()throwsInterruptedException{//…包含wait, join 或 sleep 方法Thread.sleep(1000);}

第二种方式的示例代码如下:

publicclassInterruptWaitingDemoextendsThread{@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){try{//模拟任务代码Thread.sleep(2000);}catch(InterruptedExceptione){//...清理操作//重设中断标志位Thread.currentThread().interrupt();}}System.out.println(isInterrupted());}publicstaticvoidmain(String[]args){InterruptWaitingDemodemo=newInterruptWaitingDemo();demo.start();demo.interrupt();}}

3.3、Blocked

如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正“中断”​。我们看段代码:

publicclassInterruptSynchronizedDemo{privatestaticObjectlock=newObject();privatestaticclassAextendsThread{@Overridepublicvoidrun(){synchronized(lock){while(!Thread.currentThread().isInterrupted()){}}System.out.println("exit");}}publicstaticvoidtest()throwsInterruptedException{synchronized(lock){Aa=newA();a.start();Thread.sleep(1000);a.interrupt();a.join();}}publicstaticvoidmain(String[]args)throwsInterruptedException{test();}}

test方法在持有锁lock的情况下启动线程a,而线程a也去尝试获得锁lock,所以会进入锁等待队列,随后test调用线程a的interrupt方法并调用join等待线程线程a结束,线程a会结束吗?不会,interrupt方法只会设置线程的中断标志,而并不会使它从锁等待队列中出来。

在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。如果这对程序是一个问题,应该使用显式锁。

3.4、New/Terminate

如果线程尚未启动(NEW)​,或者已经结束(TERMINATED)​,则调用interrupt()对它没有任何效果,中断标志位也不会被设置。

4、如何正确地取消/关闭线程

interrupt方法不一定会真正“中断”线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然地调用线程的interrupt方法,以为这样就能取消线程。

对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,外部调用者应该调用这些方法而不是直接调用interrupt。Java并发库的一些代码就提供了单独的取消/关闭方法,比如,Future接口提供了如下方法以取消任务:

booleancancel(booleanmayInterruptIfRunning);

再如,ExecutorService提供了如下两个关闭方法:

voidshutdown();List<Runnable>shutdownNow();
http://www.cnnetsun.cn/news/2415714.html

相关文章:

  • ESP32无线开发实战:CircuitPython Web Workflow配置与高效应用
  • Verilog仿真‘随机数’不随机?深度解析$random的种子(seed)机制与可控复现
  • 开源智能体框架xbrain:从架构设计到工程实践的完整指南
  • 开源大模型本地部署:Basaran实现OpenAI API兼容接口
  • TranslucentTB:让Windows任务栏焕然一新的轻量级透明美化工具
  • UVM配置机制深度解析:从字符串匹配原理到验证平台实战
  • DeepSeek V4 全面技术解读:正式上线状态、版本选型、迁移方案与实战避坑指南
  • VMware Workstation 17 Pro 上保姆级安装 OpenWrt 旁路由,搞定家庭网络透明代理
  • 合宙BluePill开发板:9.9元ARM Cortex-M核心板硬件解析与实战指南
  • 终极Steam饰品交易指南:如何利用挂刀行情站实现收益最大化?
  • 告别配置烦恼!用这个脚本一键搞定Win11上的JDK 1.8安装与环境变量
  • Winhance中文版:Windows系统优化与个性化管理的终极解决方案
  • Jetson NX部署避坑实录:PyTorch转TensorRT时,squeeze()和pad()函数为什么会让你的模型崩溃?
  • DayZ社区离线模式完全指南:打造你的专属末日沙盒世界
  • ESP32-S3开发板硬件选型、开发环境搭建与物联网项目实战指南
  • 别再手动装MySQL了!用Docker+Unity 2022快速搭建游戏登录系统(附完整项目)
  • 如何解决神界原罪2模组冲突问题:Divinity Mod Manager终极指南
  • Ubuntu 22.04 上 ONOS 与 Mininet 的集成部署与网络仿真实战
  • Opencv + MediaPipe -> 手势识别实战:从零搭建数字手势计数器
  • 【嵌入式实战】MPU6050:从寄存器操作到姿态解算的完整开发指南
  • 喜马拉雅VIP有声小说批量下载器:5分钟构建个人离线音频库的终极指南
  • 小米路由器R3G刷机实战:从官方固件到蜜罐版MT工具箱的保姆级避坑指南
  • DB-GPT-Hub:基于大模型微调构建专属文本到SQL数据集的实践指南
  • SAPIEN PowerShell Studio:从脚本编辑到GUI工具开发的效率革命
  • UML的范式转移:从蓝图到草图,现代软件设计的沟通演进
  • 基于铭牌数据的异步电机参数公式化精确计算
  • Arm Neoverse CMN-650架构解析与配置优化指南
  • 使用Taotoken的Token Plan套餐实现更具成本优势的持续调用
  • LaTeX中文排版难题:如何快速解决字体缺失问题?
  • 使用taotoken后ubuntu服务器调用大模型api的延迟与稳定性体验