【Java笔记】多线程:一些有关中断的理解

文章目录

  • 线程中断的作用
    • 线程的等待状态
      • WAITING
      • TIMED_WAITING
    • 线程从等待中恢复
  • `java.lang.Thread`中断实现
    • 相关方法
    • 中断标识`interrupted`
  • 一些小练习
    • `Thread.interrupt()` 只唤醒线程并修改中断标识
    • `sleep()` 清除中断状态标识
  • Reference

线程中断的作用

线程中断可以使一个线程从等待状态变成就绪状态

使用线程中断,并不是要把线程给终止或是杀死,而是让线程不再继续等待,而是让线程不再继续等待,线程可以继续往下执行代码,线程发生中断后,会抛出一个中断的异常,决定如何处理就看业务代码怎么写

线程的等待状态

详细的JVM线程状态与OS线程状态相关可以看这篇,中断等待一般是从WAITING、TIME WAITING等阻塞,或者说挂起状态(感觉更准确,一般都是主动挂起,而BLOCK阻塞状态一般是竞争锁资源失败被动阻塞到队列中),恢复到RUNNABLE。

WAITING

  • Object.wait():使当前线程处于等待状态,直到另一个线程通过notify()显式唤醒它;
  • Thread.join():插队,当前线程需要等待join的线程执行完毕,底层调用的是Object实例的wait方法;
  • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。LockSupport.unpark(), 恢复许可来唤醒

TIMED_WAITING

  • Thread.sleep(long millis):使当前线程睡眠指定时间

  • Object.wait(long timeout) :线程休眠指定时间,需要与synchronized一起使用,等待期间可以通过notify()/notifyAll()唤醒;

    Object o = new Object();
    synchronized (o){o.wait();//将当前线程挂起,并释放o锁o.notify();//唤醒一个等待在o锁等待队列的线程o.notifyAll();//唤醒等待在o锁等待队列的所有线程
    }
    
  • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;

上述三种方法使线程等待后,可以通过Interrupt()显示唤醒(中断等待

线程从等待中恢复

  • 等待超时:Thread.sleep(long millis) 等到时间了自己回复
  • 得到通知:Object.wait() 线程休眠期间,其他线程可通过notify()/notifyAll()将其唤醒;
  • 使用中断:Interrupt()

java.lang.Thread中断实现

相关方法

  • **java.lang.Thread.interrupt() :**中断目标线程,给目标线程发一个中断信号,线程被打上中断标记(设为true)。

    public void interrupt() {if (this != Thread.currentThread()) {checkAccess();// thread may be blocked in an I/O operationsynchronized (interruptLock) {Interruptible b = nioBlocker;if (b != null) {interrupted = true;interrupt0();  // inform VM of interruptb.interrupt(this);return;}}}// 打上中断标记interrupted = true;// interrupt0()是一个native方法,主要就是用cpp从OS层面将线程唤醒interrupt0();  // inform VM of interrupt
    }
    
  • **java.lang.Thread.isInterrupted() :**判断目标线程是否被中断,不会清除中断标记。

    public boolean isInterrupted() {return interrupted;
    }
    
  • **java.lang.Thread.interrupted() :**判断目标线程是否被中断,会清除中断标记。

    public static boolean interrupted() {return currentThread().getAndClearInterrupt();
    }// ...
    boolean getAndClearInterrupt() {boolean oldValue = interrupted;// 因为读取interrupted字段时也可能被中断// 此时如果直接清除interrupted,会导致这次新的中断丢失// 所以这里要判断一下if (oldValue) {interrupted = false;clearInterruptEvent(); // native方法}return oldValue;
    }
    

中断标识interrupted

Thread中有一个中断标识属性volatile boolean interrupted ,用于标记当前线程是否中断。有两点要注意:

  • Thread.interrupt()方法做两个工作:
    • 在当前线程将中断标志修改为true,并不是停止线程
    • 通过native的interrupt0()将等待的线程唤醒
  • 如果当前线程在调用Object类的wait()方法或者这个类的join()sleep()方法被打断时,会将它的中断状态将被清除(interrupted设为false),并且它会收到一个InterruptedException异常。

当线程正在等待、休眠或以其他方式被占用,并且线程在活动之前或期间被中断抛出,都会抛出InterruptedException

  • (个人理解)抛出InterruptedException,这个行为是中断的直接结果,捕获这个异常后我们可以让线程执行唤醒后的逻辑(继续运行或者return结束)

    try{Thread.sleep(3000);
    } catch (InterruptedException e) {// 打印日志或者return退出都可以// ...
    }
    // 当前线程的后续逻辑
    // ...
    
  • 线程通过抛出IE从非活跃状态(WAITING、TIMED WAITING)恢复到活跃状态(RUNNABLE)。如果线程本身就是活跃状态,则会无视RUNNABLE,也不会抛出IE。此时如果想处理中断,就需要**isInterrupted()interrupted()**来获取中断状态标识interrupted

一些小练习

Thread.interrupt() 只唤醒线程并修改中断标识

sleep、wait、join挂起线程都会修改中断标识符并抛出InterruptedException,如果没有抛出IE的情况下(比如yield后线程还是RUNNABLE状态)希望响应中断,则需要isInterrupted()interrupted()来获取中断状态标识interrupted

private static void test1(){// 程序中没有响应中断信号的逻辑,线程不会被中断Thread thread = new Thread(()->{System.out.println("线程启动");while (true){Thread.yield();}});thread.start();thread.interrupt();
}
private static void test2(){// 手动响应中断,退出线程Thread thread = new Thread(()->{System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){Thread.yield();// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断");return;}}});thread.start();thread.interrupt();
}

sleep() 清除中断状态标识

private static void test3() throws InterruptedException {// 中断失败,因为sleep会将当前线程的interrupted清楚(设为false)Thread thread = new Thread(()->{System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");return;}try{Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");}}});thread.start();Thread.sleep(2000);thread.interrupt();
}
  • 最开始,子线程start,Thread.currentThread().isInterrupted() 为false,不能进入if(Thread.currentThread().isInterrupted())直接进入try里的sleep(3s)
  • 主线程sleep(2s)
  • 2s后,主线程自己到点唤醒,通过thread.interrupt() 中断唤醒子线程,此时中断标识interrupted为true,由于sleep()抛出InterruptedException(同时将中断标识清除,设为false)被catch捕获,打印“线程休眠被中断”
  • 回到if (Thread.currentThread().isInterrupted()),由于前面sleep抛出IE的同时将中断标识设为false,所以不能执行这个if里的逻辑,结果就是一直在while里重复sleep

在这里插入图片描述

private static void test4() throws InterruptedException {Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");return;}try{Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");Thread.currentThread().interrupt();}}});thread.start();Thread.sleep(2000);thread.interrupt();
}

这里再catch到sleep的IE之后再通过Thread.currentThread().interrupt() 把当前线程的interrupted 表示符置为true,下一个循环就能执行if (Thread.currentThread().isInterrupted()) 的return逻辑了。
在这里插入图片描述
为什么呢?

public static void main(String[] args) {System.out.println(Thread.currentThread().isInterrupted());Thread.currentThread().interrupt();try {System.out.println(LocalTime.now() +": 开始睡眠");System.out.println(Thread.currentThread().isInterrupted());Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().isInterrupted());System.out.println(LocalTime.now()+": 发生中断");}System.out.println(LocalTime.now()+": 结束睡眠");
}

在这里插入图片描述

打印一下中断标识,可以看出sleep之前interrupt()已经将interrupted设为true,sleep会将其改为false并直接抛出IE

Reference

【Java并发·08】线程中断 interrupt
对于Java线程中断的理解,哪种情况下会响应中断?哪种情况下不响应中断?
Java 线程中断?看这篇就够了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/675402.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux命名管道的创建及应用

目录 一、命名管道的定义即功能 1.1创建命名管道 1.2匿名管道和命名管道的区别 1.3命名管道的打开规则 1.4系统调用unlink 二、进程间命名管道的创建及使用 2.1Comm.hhp 2.2PipeServer.cc 2.3PipeClient.cc 一、命名管道的定义即功能 管道应用的一个限制就是只能在具有…

MySQL之聚合函数与应用

1. 前言 上文我们讲到了单行函数.实际上SQL还有一类叫做聚合函数, 它是对一组数组进行汇总的函数, 输入的是一组数据的集合, 输出的是单个值. 2. 聚合函数 用于处理一组数据, 并对一组数据返回一个值. 有如下几种聚合函数 : AVG(), SUM(), MAX(), MIN(), COUNT(). 3. AVG(…

上位机图像处理和嵌入式模块部署(树莓派4b镜像烧录经验总结)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 陆陆续续也烧录了好多次树莓派的镜像了,这里面有的时候很快,有的时候很慢。特别是烧录慢的时候,也不知道是自己…

基于51单片机的闭环反馈直流电机PWM控制电机转速测量( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机的闭环反馈直流电机PWM控制转速测量( proteus仿真程序设计报告原理图讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0086 1. 主要功能: 基于51单片机的闭环…

Figma 高效技巧:设计系统中的图标嵌套

Figma 高效技巧:设计系统中的图标嵌套 在设计中,图标起着不可或缺的作用。一套便捷易用的图标嵌套方法可以有效提高设计效率。 分享一下我在图标嵌套上走过的弯路和经验教训。我的图标嵌套可以分三个阶段: 第一阶段:建立图标库 一…

语音识别--光谱门控降噪

⚠申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计7267字,阅读大概需要3分钟 🌈更多学习内容, 欢迎👏关注👀【文末】我的个人微信公众号&#xf…

数据结构复习/学习9--二叉树

一、堆与完全二叉树 1.堆的逻辑与物理结构 2.父节点与子节点的下标 3.大小根堆 二、堆的实现(大根堆为例) 注意事项总结: 注意堆中插入与删除数据的位置和方法与维持大根堆有序时的数据上下调整 三、堆排序 1.排升序建大堆效率高 注意事项…

零基础代码随想录【Day27】|| 39. 组合总和,40.组合总和II, 131.分割回文串

目录 DAY27 39. 组合总和 解题思路&代码 40.组合总和II 解题思路&代码 131.分割回文串 解题思路&代码 DAY27 39. 组合总和 力扣题目链接(opens new window) 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有…

1天搞定SpringBoot+Vue全栈开发 (7)Axios网络请求

1.Axios的使用 Axios中文文档 | Axios中文网Axios 是一个基于 promise 的网络请求库&#xff0c;可以用于浏览器和 node.jshttps://www.axios-http.cn/ 2.与vue整合 App.vue: <template><div id"app"><Moviev-for"movie in movies":key&qu…

php使用服务器端和客户端加密狗环境部署及使用记录(服务器端windows环境下部署、linux环境宝塔面板部署、客户端部署加密狗)

php使用服务器端和客户端加密狗环境部署及使用记录 ViKey加密狗环境部署1.windows环境下部署开发文档验证代码提示Fatal error: Class COM not found in 2.linux环境下部署&#xff08;宝塔面板&#xff09;开发文档验证代码提示Fatal error: Uncaught Error: Call to undefine…

pwn学习(一)

pwn:二进制漏洞挖掘与利用&#xff08;程序里面的漏洞&#xff09; CTF中的Pwn是仅保留漏洞代码和基本逻辑的二进制程序&#xff0c;选手通过自身对漏洞的熟悉程度来快速的在逆向分析中找到漏洞点&#xff0c;并且结合自身对漏洞利用的熟悉程度来编写EXP脚本&#xff0c;从而获…

动态规划算法:简单多状态问题

例题一 解法&#xff08;动态规划&#xff09;&#xff1a; 算法思路&#xff1a; 1. 状态表⽰&#xff1a; 对于简单的线性 dp &#xff0c;我们可以⽤「经验 题⽬要求」来定义状态表⽰&#xff1a; i. 以某个位置为结尾&#xff0c;巴拉巴拉&#xff1b; ii. 以某个位置为起…