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

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)

目录

LockSupport与线程中断

线程中断机制

什么是中断机制?

与中断相关的3个API

如何停止中断运行中的线程?

当前线程的中断标识为true,是不是线程就会立刻停止?

如何理解静态方法Thread.interrupted()

LockSupport是什么

线程等待和唤醒机制

3种让线程等待唤醒的方法

Object类中的wait()和notify()方法实现线程的等待和唤醒

Condition接口中的await()后signal()方法实现线程的等待和唤醒

上述两个对象Object和Condition使用的限制条件

LockSupport类中的park()等待和unpack()唤醒

总结


LockSupport与线程中断

线程中断机制

什么是中断机制?

首先,一个线程不应该由其他线程强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运(所以,Thread的stop()、suspend()、resume()都已经废弃了)

其次,在Java中没有办法立即停止一条线程,然而停止线程又显得那么重要(比如需要取消一个耗时/错误操作)。因此,Java提供了一种用于停止线程的协商机制——中断,也即中断标识协商机制

中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完成完全要求程序员自己实现。若要中断一个线程,需要手动调用该线程的interrupt()方法,该方法也仅仅是将线程对象的中断标识设成true,接着按自己的需要,写代码不断监测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要自己写代码实现。

每个线程对象中都有一个中断标识位,用于表示线程是否被中断:该标识位为true标识中断,为false表示未中断。通过调用线程对象的interrupt方法将该线程的标识位设为true,可以在别的线程中带调用,也可以在自己的线程中调用。

与中断相关的3个API
  • public void interrupt():设置线程的中断状态为true,发起一个协商而不会立刻停止线程
  • public static boolean isInterrupted():通过检查中断标识位判断线程是否被中断并清除当前中断状态(返回当前线程的中断状态+重置中断状态为false)
  • public boolean isInterrupted():通过检查中断标识位判断当前线程是否被中断
如何停止中断运行中的线程?
  • 通过volatile变量实现(线程间的可见性)
  • 通过AtomicBoolean实现

执行结果如下图:

  • 通过Thread类自带的中断API实例方法实现 在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程
    • interrupt() 中断线程

我们可以看到interrupt0()是一个native方法

除非线程正在中断(始终允许),否则将带调用此线程的checkAccess()方法(可能还会导致抛出SecurityException)

public class InterruptDemo {static volatile boolean isStop = false;public static void main(String[] args) {Thread tA = new Thread(() -> {while (true) {if (isStop) {System.out.printf(Thread.currentThread().getName() + " isStop 被修改为true!");break;}System.out.println("-------------------->");}}, "tA");tA.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {isStop = true;}, "B").start();}
}

执行结果如下图:

public class InterruptDemo {static AtomicBoolean atomicBoolean = new AtomicBoolean(false);public static void main(String[] args) {Thread tA = new Thread(() -> {while (true) {if (atomicBoolean.get()) {System.out.printf(Thread.currentThread().getName() + " atomicBoolean 被修改为true!");break;}System.out.println("-------------------->");}}, "tA");tA.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {atomicBoolean.set(true);}, "B").start();}
}
package com.aqin.juc;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;/*** @Description* @Author aqin1012 AQin.* @Date 10/9/23 11:29 AM* @Version 1.0*/
public class InterruptDemo {public static void main(String[] args) {Thread tA = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.printf(Thread.currentThread().getName() + " isInterrupted() 被修改为true!程序停止!");break;}System.out.println("-------------------->");}}, "tA");tA.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {tA.interrupt();}, "B").start();}
}

执行效果如下图:

当前线程的中断标识为true,是不是线程就会立刻停止?

不会,具体来说,对一个线程,调用interrupt()时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true(仅此而已),被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不是真正的中断线程,需要被调用的线程自己进行配合才行。如果线程处于被阻塞状态(如sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt()方法时,那么线程将会立即退出被阻塞状态,并抛出一个InterruptedException异常。

如何理解静态方法Thread.interrupted()

这个方法是判断线程是否中断并清除当前中断状态的,简单来说就是“复位”。

主要做了两件事:

  1. 返回当前线程的中断状态,测试当前线程是否已经被中断
  2. 将当前线程的中断状态清零并重新设置为false,清除线程的中断状态

那么问题来了,这个静态方法interrupted()跟实例方法isInterrupted()有什么区别呢?

我们举个简单的例子对比下执行结果

静态方法interrupted()

执行结果如下:

实例方法isInterrupted()

执行结果如下

来看它俩的源码对比

可以看到其实它们调用的是同一个方法,只不过,在对于是否需要清理这个参数的传递上,静态方法传递的是true,而实例方法传递的是false,简单来讲就是多了一步“复位”操作。因此,才会出现上面两段示例代码执行的结果不一致,在当前线程未执行中断方法interrupt()时,当前线程的中断标识位本身就是初始值false,因此连续调用两次静态方法或者实例方法的执行结果都是false;当当前线程执行了中断方法interrupt()时,第一次调用静态方法和实例方法的返回值同样都是true,但是此时静态方法多做了一步“复位”操作,把当前线程的中断标识位重置回了初始值false,而实例方法则没有这步操作,因此,当第二次调用时,实例方法的示例中当前线程的中断标识位仍然是true,因此仍然返回true,而静态方法的示例代码中当前线程的中断标识位已经被重置回了false,于是就返回了false。

LockSupport是什么

LockSupport是java.util.concurrent.locks中的一个类,用于创建锁和其他同步类的基本线程阻塞原语

线程等待和唤醒机制

3种让线程等待唤醒的方法
  1. 使用Object中的wait()方法让线程等待,使用notify()方法唤醒线程
  2. 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  3. 使用LockSupport类中的park()和unpark()方法阻塞当前线程以及唤醒指定被阻塞的线程
Object类中的wait()和notify()方法实现线程的等待和唤醒

示例代码:

public static void main(String[] args) {Object objectLock = new Object();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (objectLock) {System.out.println(Thread.currentThread().getName() + " 进入--->");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");}}, "A").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {synchronized (objectLock) {objectLock.notify();}System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");}, "B").start();
}

执行结果如下:

问题:

  • 必须要先持有锁,否则会报错

  • 必须先wait()再notify(),否则会卡死

Condition接口中的await()后signal()方法实现线程的等待和唤醒

示例代码:

public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace();}lock.lock();System.out.println(Thread.currentThread().getName() + " 进入--->");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");}, "A").start();try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace();}new Thread(() -> {lock.lock();try {condition.signal();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");}, "B").start();}

执行结果如下:

问题:

  • 必须要先持有锁,否则会报错

在lock、unlock对里面才能正确调用condition中线程等待和唤醒的方法。

  • 必须先await()再signal(),否则会卡死

上述两个对象Object和Condition使用的限制条件
  • 线程先要获得并持有锁,必须在锁块(synchronized/lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒
LockSupport类中的park()等待和unpack()唤醒

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每一个线程都有一个Permit(许可),但与Semaphore不同的是,许可的累加上限是1(最多一个许可)。

使用示例:

public static void main(String[] args) {Thread A = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 进入--->");LockSupport.park();System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");}, "A");A.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {LockSupport.unpark(A);System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");}, "B").start();
}

执行结果如下:

可以看到,使用LockSupport进行线程阻塞/唤醒不需要在锁对块(synchronized/lock)中,所以上面Object类和Condition接口的第一个问题解决了,然后我们看看第二个问题:加锁/解锁的先后顺序。

如上图可以看出,park()和unpack()执行的先后顺序不会影响结果。

总结

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport是一个线程阻塞工具,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞后也有对应的唤醒方法,归根结底,LockSupport调用的是Unsafe中的native代码。

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,每个使用LockSupport的线程都一个Permit(许可)与LockSupport相关联,每一个线程都都一个相关的Permit,并且最多一个,重复调用unpark()也不会积累Permit。

换句话讲,线程阻塞需要消耗凭证,并且这个凭证最多1个

当调用park()方法时:

  • 如果有Permit,会消耗这个Permit,然后正常退出
  • 如果没有Permit,会阻塞当前线程等待获取凭证

当调用unpark()方法时:

  • 会增加一个Permit
  • 调用多次也只会有一个Permit(不累加)

最后,我们思考2个问题:

  1. 为什么LockSupport提供park()和unpark()方法可以突破Object和Condition的调用先后顺序的限制? 因为调用unpark()会获得1个Permit,之后再调用park()会消耗这个Permit,park()和unpark()方法的执行顺序不会影响线程的唤醒,即便先发放了Permit仍然不会阻塞线程。
  2. 唤醒两次(调用两次park()方法)后阻塞两次(调用两次unpark)方法),会发生什么情况? 线程会被阻塞,因为Permit不会累加,即使调用了两次unpark(),仍然只会有一个Permit,后面接着的调用两次park(),在调用第一次park()时,就会把这个Permit消耗掉,第二次调用时就没有Permit了,因而会被阻塞。

搞定( ̄∇ ̄)/🎉🎉🎉~~~~~~~~~~

相关文章:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)

目录 LockSupport与线程中断 线程中断机制 什么是中断机制? 与中断相关的3个API 如何停止中断运行中的线程? 当前线程的中断标识为true,是不是线程就会立刻停止? 如何理解静态方法Thread.interrupted() LockSupport是什么…...

Springboot学习笔记——3

Springboot学习笔记——3 一、热部署1.1、手动启动热部署1.2、自动启动热部署1.3、热部署范围配置1.4、关闭热部署 二、配置高级2.1、第三方bean属性绑定2.2、松散绑定2.3、常用计量单位应用2.4、bean属性校验2.5、进制数据转换规则 三、测试3.1、加载测试专用属性3.2、加载测试…...

jupyter 切换虚拟环境

当前只有两个环kernel 我已经创建了很多虚拟环境,如何在notebook中使用这些虚拟环境呢?请看下面 比如说我要添加nlp 这个虚拟环境到notebook中 1. 切换到nlp环境 2. 安装如下模块 pip install ipykernel 3. 执行如下命令 python -m ipykernel install …...

如何在Apache和Resin环境中实现HTTP到HTTPS的自动跳转:一次全面的探讨与实践

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...

安全防御—密码学

1. 什么是APT? APT(Advanced Persistent Threat)是指高级持续性威胁,本质是针对性攻击。 利用先进的攻击手段对特定目标进行长期持续性网络攻击的攻击形式,APT攻击的原理相对于其他攻击形式更为高级和先进,…...

灯具从深圳寄国际物流到墨西哥

在国际贸易的日益频繁的今天,越来越多的企业开始将产品销往海外市场。然而,如何将这些产品安全、快速地送达目的地,成为了每个企业都需要面对的问题。对于灯具这种重量大、体积大的物品来说,如何选择合适的国际物流方式&#xff0…...

spark3使用hive zstd压缩格式总结

ZSTD(全称为Zstandard)是一种开源的无损数据压缩算法,其压缩性能和压缩比均优于当前Hadoop支持的其他压缩格式,本特性使得Hive支持ZSTD压缩格式的表。Hive支持基于ZSTD压缩的存储格式有常见的ORC,RCFile,Te…...

直线导轨精度等级在设备中有什么影响?

直线导轨的精度选择是直线导轨应用中的重要环节,需要根据具体的应用场景和设备要求来选择合适的精度等级(常见分3个等级:N/H/P)。下面我们来详细了解一下直线导轨的精度选择。 1、精度等级的概念:直线导轨的精度等级是…...

windows平台FairMOT的实现

环境:python3.6pytorch1.1.0torchvision0.3.0cuda9.2vs2015 该项目需要装3个c库(dcn_v2,apex,cython_bbox)特别坑,各种环境不匹配,各种bug。本人c小白,但是一路摸索总算成功了。下面…...

系统架构设计:12 论软件维护方法及其应用

目录 一 软件维护方法 1 影响软件维护工作的因素 2 软件维护类型 (1)正确性维护...

SS命令使用介绍

ss 一. 命令介绍 先使用手册查看命令介绍信息 NAME ss - another utility to investigate sockets DESCRIPTION ss is used to dump socket statistics. It allows showing information similar to netstat. It can display more TCP and state informations than other tools.…...

让你的对象变得拗口:JSON.stringify(),我把对象夹进了 JSON 魔法帽!

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 引言 1. JSON.stringify() 属性 replacer …...

TDengine时序数据库学习使用

数据库要求: 1.目前服务器只能在linux运行,先安装服务器版本v1 2.下载与v1完全匹配的客户端版本v1(客户端与服务器的版本号不匹配可能访问不了服务器) 第一步 安装 安装服务器注意,安装教程: 使用安装…...

算法通过村第十三关-术数|青铜笔记|数字与数学

文章目录 前言数字统计专题符号统计阶乘0的个数 溢出问题整数反转字符串转整数回文数 进制专题七进制数进制转换 总结 前言 提示:生活是正着来生活,倒着去理解。 --戴维迈尔斯《社会心理学》 数学是学生时代掉头发的学科,那算法是毕业后掉头发…...

【SpringMVC篇】详解SpringMVC入门案例

🎊专栏【SpringMVC】 🍔喜欢的诗句:天行健,君子以自强不息。 🎆音乐分享【如愿】 🎄欢迎并且感谢大家指出小吉的问题🥰 文章目录 🎍SpringMVC简介⭐优点 🌺SpringMVC入门…...

Programming abstractions in C阅读笔记:p166-p175

《Programming Abstractions In C》学习第58天,p166-p175总结。 一、技术总结 1.斐波那契数列(Fibonacci Sequenc) (1)斐波那契数列来源 斐波那契数列来自于《Liber Abaci》一书里兔子繁殖问题,相关资料很多,这里不赘述。 (2)关于《Libe…...

【List-Watch】

List-Watch 一、定义二、工作机制三、调度过程 一、定义 Kubernetes 是通过 List-Watch 的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 …...

Pytorch因nn.Parameter导致实验不可复现的一种情况

文章首发见博客:https://mwhls.top/4871.html。 无图/格式错误/后续更新请见首发页。 更多更新请到mwhls.top查看 欢迎留言提问或批评建议,私信不回。 没解决,只是记录这种情况。 也可以多次实验取均值以避免结果复现。 场景 自己的模块中&a…...

MySQL表名区分不区分大小写,规则是怎样

MySQL表名区分不区分大小写,规则是怎样 mysql在linux中表名区分大小写,mysql在Windows中表名不区分大小写;可以在MySQL的配置文件“my.ini [mysqld]”中增加一行“lower_case_table_names 参数”来设置是否区分大小写。 mysql的表名区分大小写…...

Design patterns--观察者模式

设计模式之观察者模式 代码示例 #ifndef OBSERVER_H #define OBSERVER_H#include <map>class Observer { public:Observer();virtual void update(std::map<int, double>) 0; }; #endif // OBSERVER_H#include "observer.h"Observer::Observer() {}#if…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...