【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 operation
            synchronized (interruptLock) {
                Interruptible b = nioBlocker;
                if (b != null) {
                    interrupted = true;
                    interrupt0();  // inform VM of interrupt
                    b.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 线程中断?看这篇就够了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无处不在的AI:被科技巨头盯上的Agent智能体的崭新时代

🥽一.Agent AI智能体 Agent AI 智能体是一种基于人工智能技术的智能代理,它可以自主地执行任务、与环境进行交互,并根据环境的变化做出决策。 OpenAI将AI Agent定义为以大语言模型(LLM)为大脑驱动具有自主理解、感知、…

关于电商API接口【满足高并发大批量请求】||电商API接口入门指南

简介: API(应用程序编程接口)是一种让不同软件之间进行通信的方式。在电子商务中,电商API接口可以用于获取商品信息、下单、支付等等。本篇文章将介绍电商API接口的入门知识,并提供示例代码以帮助你快速上手。 一、了解…

言出身随!人情世故:利益交换与人脉的重要性——早读(逆天打工人爬取热门微信文章解读)

巴黎输了,看了比赛还得加班 引言Python 代码第一篇 洞见 认知越高的人,越懂得感恩第二篇 冯站长之家 2024年5月8日(周三)三分钟新闻早餐结尾 智慧赋予我决策的明灯 勇气则是我行动的盾牌 在细雨中骑行 是我以智慧选择的道路 用勇气…

Foxmail邮箱API发送邮件失败的原因有哪些?

Foxmail邮箱API发送邮件的注意事项?如何用API发信? 在使用Foxmail邮箱API发送邮件时,有时会遇到发送失败的情况。这种情况可能由多种原因造成,下面AokSend就来详细探讨一下Foxmail邮箱API发送邮件失败的可能原因。 Foxmail邮箱A…

Ubuntu18.04 安装 anconda

anaconda官网 bash Anaconda3-2021.11-Linux-x86_64.sh一直回车,输入yes 选择安装目录 是否希望更新shell配置文件以自动初始化conda

4diacIDE同时编译不同版本踩坑记录

4diac不同版本依赖插件版本及jdk版本是不同的,当你需要搭建不同版本4diacIDE开发环境时,就会出现各种问题。最近一个月github上项目提交记录比较多,出现了不少坑。以下记录下此背景下的解决方法: 1、首先由于.target依赖的eclipse…

java项目跑不起来 端口已被使用

背景 Springboot项目跑不起来,原因端口被占用。 解决方法 在 Windows 环境下,你可以按照以下步骤来查看某个端口被占用的情况,并停止相应的进程: 查看所有端口占用情况: 按下 Win R 键,打开运行窗口。…

C语言内存函数memcpy与memmove

一.memcpy的使用和模拟实现 1.函数原型 void* memcpy(void* destination, const void* source, size_t num); destination是目标内存块的指针 source是源内存块的指针 num是要复制的字节数 .函数memcpy从source的位置开始向后复制 num个字节 的数据到destination指向的内存位置…

YOLO系列自研改进:基于注意力机制的多尺度特征提取模块

目录 一、原理 二、代码 三、在YOLO中的应用 一、原理 这个模块的原理仍然是利用不同大小的卷积核来提取不同尺度的特征,同样将通道划分为两部分,一部分通过注意力机制进行通道信息和空间信息的提取,另一部分通过多个不同大小的卷积核来提取多尺度的特征信息。 二、代码…

【Unity】使用Resources.LoadAll读取文件的顺序问题

最近在做客户的一个项目,其中的一个模块使用到了照片,但是发现了一个很严重的问题。当你在使用Unity的时候,它竟然不按照顺序读取?这个机器人是不是逻辑有问题?如下图: 名字脱敏了哈。。。 照片比较多&…

await执行顺序

用一个简单的例子来说明,await到底在等待什么 求代码的打印顺序 function testAsy(x) {return new Promise((resolve) > {setTimeout(() > {resolve(x);}, 3000);}); } async function testAwt() {console.log("async开始执行");// 最先打印let r…

Sketch for Mac v100 中文激活版——设计师必备的矢量绘图软件

在数字化时代,设计无处不在,而Sketch for Mac正是一款专为设计师量身打造的设计利器。其简洁直观的操作界面、强大的功能和高效的工作流程,让您的创意无限释放。 Sketch for Mac v100 中文激活版下载 Sketch支持矢量图形编辑,无论…

机试:字符串相关简单问题

字符移动问题 这道题的描述是这样的:输入一个字符串,将其中的数字字符移动到非数字字符之后,并保持数字字符和非数字字符输入时的顺序。例如:输入字符串“ab4f35gr#a6”,输出为“abfgr#a4356”。 以下使我试着敲的代码&#xff…

Leetcode—138. 随机链表的复制【中等】(cend函数)

2024每日刷题(129) Leetcode—138. 随机链表的复制 实现代码 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;random NULL;} }; */class Solution { public:Node* copyRan…

QT+多线程TCP服务器+进阶版

针对之前的服务器,如果子线程工作类里面需要使用socket发送消息,必须要使用信号与槽的方法, 先发送一个信号给父进程,父进程调用socket发送消息(原因是QT防止父子进程抢夺同一资源,因此直接规定父子进程不能…

雷蛇笔记本数据丢失怎么恢复?提供详细指南

在数字化时代,笔记本电脑已成为我们日常生活和工作中不可或缺的一部分。然而,尽管技术不断进步,数据丢失的风险仍然存在。雷蛇(Razer)作为一家知名的电脑硬件制造商,其笔记本电脑也难免会遇到这样的问题。当…

数学建模资料|历年数维杯数学建模竞赛真题及获奖论文汇总

2024年第九届数维杯大学生数学建模挑战赛:2024年5月10日08:00-5月13日09:00举行,为了更好的帮助参赛同学了解竞赛的赛制及赛题特点,数乐君今天给大家整理了历年数维杯国赛真题及优秀论文,方便同学们赛前巩固训练,掌握解题方法,提高获奖率。 2023年数维杯国赛真题(ABC题…

使用Maven对Java独立应用程序进行编译打包

一、 安装Maven 1.解压,移动安装包 sudo tar -zxf ~/apache-maven-3.9.6-bin.tar.gz -C /usr/local/ cd /usr/local/ sudo mv apache-maven-3.9.6/ ./maven-3.9.6 sudo chown -R qiangzi ./maven-3.9.6 二、Java应用程序代码 1.版本信息: Spark-2.1…

Python Flask框架(一)初识Flask

Flask是使用Python编写的Web微框架。Web框架可以使我们不用关心底层的请求响应处理,更方便高效的编写Web程序。Flask有两个主要依赖,一个是WSGI(Web Server Gateway Interface,web服务器网关接口)工具集,另…

ansible——INVENTORY主机清单

一、Inventory主机清单 Inventory支持对主机进行分组,每个组内可以定义多个主机,每个主机都可以定义在任何一个或多个主机组内 二、Inventory主机清单部署 2.1 前期准备 systemctl stop firewalld setenforce 0 yum install epel-release -y yum install…