线程阻塞和线程中断

news2024/11/18 20:00:26

本专栏学习内容又是来自尚硅谷周阳老师的视频

有兴趣的小伙伴可以点击视频地址观看

中断机制

简介

Java线程中断机制是一种用于协作式线程终止的机制。它通过将一个特殊的中断标志设置为线程的状态来实现。当线程被中断时,它可以检查这个中断标志并采取相应的措施来停止线程的执行。

首先

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己决定自己的命运

其次

在Java中没有办法立刻停止一条线程,中断也只是一种协商机制,Java并没有给中断增加任何语法,中断的过程需要程序员自己实现。

三大中断方法说明

方法名作用
void interrupt()中断这个线程
static boolean interrupted()测试当前线程是否中断。 该方法可以清除线程的中断状态
boolean isInterrupted()测试这个线程是否被中断,true代表中断

线程中断实现

小黄认为线程中断是一种编程的思想,例如A线程需要中断,应该是A线程去监听某一个数据,当这个数据改变时,中断该线程

while(true) {
    if (xxx) {
        //线程中断
        break;
    }
}

线程中断应该是由被中断线程自己操作的,例如在餐厅中,服务员来提醒小明此地禁止吸烟,然后由小明将烟掐灭,而不是服务员上去直接把烟掐灭

通过volatile关键字

volatile 是 Java 中的一个关键字,用于修饰变量。当一个变量被声明为 volatile 时,表明这个变量是被多个线程共享的,并且在多线程环境下具有特殊的可见性和禁止重排序的特性。

public class VolatileDemo {
    static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + "掐灭烟头");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "正在吸烟。。。");
            }
        },"t1").start();

        TimeUnit.MILLISECONDS.sleep(20);

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "  禁止吸烟!!");
            flag = true;
        },"t2").start();
    }
}

从执行结果可以看出,t2线程发出指令后,t1线程中断了,至于多输出了一次是因为上一次循环还没有执行结束

image-20230728133417260

使用AtomicBoolean

AtomicBoolean 是 Java 中 java.util.concurrent.atomic 包提供的一个原子布尔类型,用于在多线程环境中进行原子性的布尔操作。

使用原理跟volatile差不多,都是通过其他线程改变监听对象属性来中断线程

public class AtomicDemo {
    static AtomicBoolean flag = new AtomicBoolean(false);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (flag.get()) {
                    System.out.println(Thread.currentThread().getName() + "掐灭烟头");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "正在吸烟。。。");
            }
        },"t1").start();

        TimeUnit.MILLISECONDS.sleep(20);

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "  禁止吸烟!!");
            flag.set(true);
        },"t2").start();
    }
}

使用Thread API

需要isInterrupted()interrupt()配合使用

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "掐灭烟头");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "正在吸烟。。。");
            }
        }, "t1");
        t1.start();

        TimeUnit.MILLISECONDS.sleep(20);

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "  禁止吸烟!!");
            t1.interrupt();
        },"t2").start();
    }
}

源码分析

interrupt()

interrupt()调用了interrupt0(),而interrupt0()是本地方法,也就是由c++写的

特别需要注意的是interrupt0()旁边的注释Just to set the interrupt flag只是改变状态,并不是中断线程

image-20230728135445072

isInterrupted()

isInterrupted()底层调用的也是一个本地方法,传了一个false进去

当参数为false时,isInterrupted(false)方法仅返回当前线程的中断状态,但不会清除中断状态。当参数为true时,isInterrupted(true)方法在返回中断状态之后会将中断状态重置为false,相当于清除了中断状态。

image-20230728135940557

深度解析案例

先来看一下API文档,有两个特别需要注意的地方

  1. 如果线程正在阻塞状态,调用该线程的interrupt()会将中断状态清楚,并且收到异常
  2. 中断不存在的线程没有任何效果

image-20230728140232516

案例

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 30; i++) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + ":中断");
                break;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("===interrupt=== : " + i);
        }
    }, "t1");
    t1.start();
    TimeUnit.MILLISECONDS.sleep(2000);
    t1.interrupt();
}

可以从结果看出,在现场休眠时中断线程,会抛出异常,但是线程会继续运行,这是因为会清除中断状态

image-20230728143751240

解决方案:在捕获异常时,再次中断线程

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 30; i++) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + ":中断");
                break;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                //重新中断线程
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
            System.out.println("===interrupt=== : " + i);
        }
    }, "t1");
    t1.start();
    TimeUnit.MILLISECONDS.sleep(2000);
    t1.interrupt();
}

静态方法interrupted()

interrupted()会将线程中断,并且清除线程的中断状态

public static void main(String[] args) {
    System.out.println(Thread.interrupted());//false
    System.out.println(Thread.interrupted());//false
    Thread.currentThread().interrupt();
    System.out.println(Thread.interrupted());//true
    System.out.println(Thread.interrupted());//false
}

可以看一下源码,跟isInterrupted()一样,调用的都是本地方法isInterrupted(),不过传参为true,方法在返回中断状态之后会将中断状态重置为false,相当于清除了中断状态。

image-20230728144608999

LockSupport

简介

LockSupport 是 Java 并发包中的一个工具类,提供了线程阻塞和唤醒的能力,说到线程阻塞和唤醒就不得不提到两种已经学过的方式

  • synchronized配合wait()notify()
  • Lock配合await()signal()

wait()、notify()实现阻塞和唤醒

执行以下代码,可以实现阻塞和唤醒,这里需要了解的是wait()会暂时释放锁,直到被唤醒之后重新排队获取锁

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    new Thread(() -> {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + "----come in");
                object.wait();
                System.out.println(Thread.currentThread().getName() + "----被唤醒");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    },"t1").start();

    TimeUnit.MILLISECONDS.sleep(200);

    new Thread(() -> {
        synchronized (object) {
            object.notify();
            System.out.println(Thread.currentThread().getName() + "----发送通知");
        }
    },"t2").start();
}

//结果
t1----come in
t2----发送通知
t1----被唤醒

await()、signal()实现阻塞和唤醒

执行以下代码,可以实现阻塞和唤醒

public static void main(String[] args) throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "----come in");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "----被唤醒");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            lock.unlock();
        }
    }, "t1").start();

    TimeUnit.MILLISECONDS.sleep(200);

    new Thread(() -> {
        lock.lock();
        try {
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "----发送通知");
        }finally {
            lock.unlock();
        }
    }, "t2").start();
}

缺点

上述两种代码从功能上都可以实现线程的阻塞和唤醒,但是都有两个相同的缺点

必须要加锁

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + "----come in");
            object.wait();
            System.out.println(Thread.currentThread().getName() + "----被唤醒");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }, "t1").start();

    TimeUnit.MILLISECONDS.sleep(200);

    new Thread(() -> {
        object.notify();
        System.out.println(Thread.currentThread().getName() + "----发送通知");
    }, "t2").start();
}

如果不加锁,程序会报错

image-20230728165836442

必须先阻塞,在唤醒

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    new Thread(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + "----come in");
                object.wait();
                System.out.println(Thread.currentThread().getName() + "----被唤醒");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    },"t1").start();

    new Thread(() -> {
        synchronized (object) {
            object.notify();
            System.out.println(Thread.currentThread().getName() + "----发送通知");
        }
    },"t2").start();
}

如果先唤醒,程序会一直阻塞

image-20230728170010343

LockSupport使用

先介绍一下主要的API,有点类似于上高速公路,进入高速时领取一个通行证,出高速归还通行证

方法作用
static void park()禁止当前线程进行线程调度,除非许可证可用。
static void unpark(Thread thread)为给定的线程提供许可证(如果尚未提供)。

代码

从使用上就可以看出它没有加锁释放锁的过程

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "----come in");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "----被唤醒");
    }, "t1");
    t1.start();

    TimeUnit.MILLISECONDS.sleep(200);

    new Thread(() -> {
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "----发送通知");
    }, "t2").start();
}

优势

它可以先颁发通行证,当线程执行到需要通行证时,会直接通过,不再等待

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "----come in");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "----被唤醒");
    }, "t1");
    t1.start();

    new Thread(() -> {
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "----发送通知");
    }, "t2").start();
}

//结果
t2----发送通知
t1----come in
t1----被唤醒

重点说明

对于一个线程来说,最多只能拥有一个通行证

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "----come in");
        LockSupport.park();
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "----被唤醒");
    }, "t1");
    t1.start();

    new Thread(() -> {
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "----发送通知");
    }, "t2").start();
}

执行上述代码,虽然调用了两次unpark()但是t1还是只拥有一个通行证,导致程序阻塞

image-20230728171036692

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TSINGSEE青犀视频安防监控管理平台EasyNVR如何配置鉴权?

视频监控汇聚平台EasyNVR是基于RTSP/Onvif协议的视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端的分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xff0c;我们也提供了丰富的API接口供…

无涯教程-jQuery - show( speed, callback )方法函数

show(speed&#xff0c;[callback])方法使用优美的动画显示所有匹配的元素&#xff0c;并在完成后触发可选的回调。 show( speed, [callback] ) - 语法 selector.show( speed, [callback] ); 这是此方法使用的所有参数的描述- speed - 代表三个预定义速度("slow&q…

No100.精选前端面试题,享受每天的挑战和学习(事件循环)

文章目录 1. 请解释一下JavaScript中的事件循环&#xff08;Event Loop&#xff09;是什么&#xff0c;并描述其工作原理。2. 请解释一下JavaScript中的宏任务&#xff08;macro-task&#xff09;和微任务&#xff08;micro-task&#xff09;的区别3. 在事件循环中&#xff0c;…

PS - Photoshop 抠图与剪贴蒙版功能与 Stable Diffusion 重绘

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131978632 Photoshop 的剪贴蒙版是一种将上层图层的内容限制在下层图层的形状范围内的方法&#xff0c;也就是说&#xff0c;上层图层只能在下层图…

PLC的高端版本通常具有以下特点:

高速处理能力&#xff1a;高端PLC通常具有更快的处理速度和更高的运行频率&#xff0c;可以处理更复杂的控制逻辑和更多的输入/输出信号。 大容量存储&#xff1a;高端PLC通常具有更大的存储容量&#xff0c;可以保存更多的程序和数据&#xff0c;以满足更复杂的应用需求。 多种…

Java 源码打包 降低jar大小

这里写目录标题 Idea maven 插件配置pom.xml 配置启动技巧 Idea maven 插件配置 pom.xml 配置 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- 只…

html学习1

1、<!DOCTYPE html>用来告知 Web 浏览器页面使用了哪种 HTML 版本。 2、对于中文网页需要使用 <meta charset"utf-8"> 声明编码&#xff0c;否则会出现乱码。 3、html的结构图&#xff0c;<body> </body>之间的部分可以显示。 4、HTML元素…

Circuit hdu7322

Problem - 7322 题目大意&#xff1a;有一个n个点的边权有向图&#xff0c;求图中环的权的最小值以及相应最小环的数量 1<n<500 思路&#xff1a;要求一个如下图的环的大小&#xff0c;我们只需知道u到v的最短路径加上v到u的边权 这样的话我们需要求出任意两点之间的最…

【matlab】机器人工具箱快速上手-正运动学仿真(代码直接复制可用)

安装好机器人工具箱&#xff0c;代码复制可用&#xff0c;按需修改参数 1.建模 %%%%%%%%SCARA机器人仿真模型 l[0.457 0.325]; L(1) Link(d,0,a,l(1),alpha,0,standard,qlim,[-130 130]*pi/180);%连杆1 L(2)Link(d,0,a,l(2),alpha,pi,standard,qlim,[-145 145]*pi/180);%连杆…

前端开发Vue3.0 标签setup语法『UI组件库』之『模态框』【业务提升必备】

封装模态框需要定义的参数 title //弹窗标题 show // 是否显示弹窗 width // 弹窗宽度 height // 弹窗高度 borderRadius // 弹窗圆角 headerColor // 弹窗顶部颜色 contentText // 内容文本 contentTextCorder //内容文本颜色 position // 标题的位置 …

暴力猴插件开发简明教程->百度首页默认设置为我的关注

文章目录 暴力猴插件开发简明教程->百度首页默认设置为我的关注缘起缘灭思路实现尾声 暴力猴插件开发简明教程->百度首页默认设置为我的关注 缘起 在我的百度首页有很多自己设置的导航链接(接近100个),里面放了我常用的网站, 如下图 但是最近一段时间, 我发现百度做了一…

腾讯云 CODING 成为首批 TISC 企业级平台工程综合能力要求标准贡献单位

点击链接了解详情 今天&#xff0c;由中国信通院主办的“2023 数字生态发展大会”暨中国信通院“铸基计划”年中会议在京召开。本次大会全面总结了“铸基计划”上半年度工作成果&#xff0c;帮助行业解析数字化转型发展趋势&#xff0c;链接供给侧和需求侧企业&#xff0c;以期…

2022年数学建模国赛c题论文+代码(附详解)

古代玻璃制品化学成分的分析与研究 摘要 古代玻璃极易受埋藏环境的影响而风化&#xff0c;并且在风化过程中&#xff0c;内部元素与环境元素进行着大量交换&#xff0c;导致其成分比例会发生变化&#xff0c;从而会影响对其类别的正确判断。玻璃在炼制的过程中需要添加助熔剂…

【Git】修改文件版本回退撤销修改删除文件

文章目录 修改文件版本回退git reset语法规则注意 撤销修改情况1&#xff1a;工作区的代码还未add情况2&#xff1a;工作区的代码已经add 但未commit情况3&#xff1a;工作区的代码已经add 并且已经 commit 删除文件 修改文件 Git⽐其他版本控制系统设计得优秀&#xff0c;Git…

postgresgl数据库的部署与优化

文章目录 一.postgresgl数据库1.postgresgl数据库的概念1.1 PostgreSQL 的核心概念 2.PostgreSQL特点3.PostgreSQL的作用4.PostgreSQL的应用场景5.PostgreSQL、mysql、oracle的对比 二.Linux系统安装PostgresSQL&#xff08;Centos7&#xff09;1.更新yun源2.安装PostgreSQL2.1…

Vue的router学习

,前端路由的核心是什么呢&#xff1f;改变URL&#xff0c;但是页面不进行整体的刷新。 vue-router是基于路由和组件的  路由用于设定访问路径, 将路径和组件映射起来&#xff1b;  在vue-router的单页面应用中, 页面的路径的改变就是组件的切换&#xff1b; 使用router需要…

QT 使用串口

目录 1.1.1 添加库&#xff0c;添加类 1.1.2 定义串口 1.1.3 搜索串口 1.1.4 设置和打开串口 1.1.5 读取数据 1.1.6 发送数据 1.1.7 关闭串口 1.1.1 添加库&#xff0c;添加类 首先&#xff0c;QT5 是自带 QSerialPort(Qt5 封装的串口类)这个类的&#xff0c;使用时…

单相锁相环原理与代码实战解释

单相锁相环程序原理如下图所示 单相锁相环原理 锁相环&#xff08;PLL&#xff09;是一种常用于同步、解调和信号处理等领域的电路或数字算法&#xff0c;其主要作用是将一个输入信号的相位与频率与参考信号进行精确的匹配。这里我们来简单解释一下单相锁相环的原理和分析。 …

自媒体行业下滑的5个标志

我是卢松松&#xff0c;洞察草根行业趋势&#xff01;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 未来大量的自媒体人将面临失业&#xff0c;80%的自媒体人收益没办法养活自己。前两天卢克文说自媒体的发展其实已经到达巅峰期&#xff0c;慢慢开始走下坡路了。我认…

Java集合框架的全面分析和性能增强

Java集合框架的全面分析和性能增强 &#x1f497;摘要&#xff1a;&#x1f497; 1. Java集合框架概述&#x1f497;1.1 Collection接口1.1.1 List接口1.1.2 Set接口1.1.3 Queue接口 &#x1f497;1.2 Map接口 &#x1f497;2. Java集合框架性能优化&#x1f497;2.1 选择合适的…