当前位置: 首页 > 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…...

【Spring Boot】SpringBoot 单元测试

SpringBoot 单元测试 一. 什么是单元测试二. 单元测试的好处三. Spring Boot 单元测试单元测试的实现步骤 一. 什么是单元测试 单元测试&#xff08;unit testing&#xff09;&#xff0c;是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。 二. 单元测试的好处…...

ansible 调研

参考&#xff1a;自动化运维工具——ansible详解&#xff08;一&#xff09; - 珂儿吖 - 博客园 (cnblogs.com) ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xf…...

QT UI控件汇总介绍

按钮 ToolButton 和pushbutton没什么区别&#xff0c;可以用来设置图标 设置展示策略 RadioButton 一般用Container可以将其框起来设置互斥域&#xff0c;推荐选用GroupBox 使用方法 qDebug()<<ui->radioButton_3->isChecked(); CheckBox 可以勾选三态 stat…...

【垃圾回收概述及算法】

文章目录 1. 垃圾回收概述及算法2. 垃圾回收相关算法2.1 标记阶段&#xff1a;引用计数算法2.2 标记阶段&#xff1a;可达性分析算法2.3 对象的 finalization 机制2.3.1 一个对象是否可回收的判断 2.4 清除阶段&#xff1a;标记-清除算法2.5 清除阶段&#xff1a;复制算法2.6 清…...

2021年03月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 对于字典infor {“name”:“tom”, “age”:13, “sex”:“male”}&#xff0c;删除"age":13键值对的操作正确的…...

为什么通过一致性正则化方法就可以避免将所有未标记数据集分配给同一类?

一致性正则化方法可以帮助避免将所有未标记数据分配给同一类别的原因在于它们引入了对模型输出的一致性约束&#xff0c;从而减轻了判别性损失&#xff08;如交叉熵损失&#xff09;可能导致的问题。以下是一些关键原因&#xff1a; 一致性反馈&#xff1a; 一致性正则化方法…...

第4章 决策树

文章目录 4.1 基本流程4.2 划分选择4.2.1 信息增益4.2.2 增益率4.2.3 基尼指数 4.3 剪枝处理4.3.1 预剪枝4.3.2 后剪枝 4.4 连续与缺失值4.4.1 连续值处理4.4.2 缺失值处理 4.5 多变量决策树4.6 阅读材料 4.1 基本流程 决策树也称判定树&#xff0c;是一类常见的机器学习方法。…...

在Remix中编写你的第一份智能合约

智能合约简单来讲就是&#xff1a;部署在去中心化区块链上的一个合约或者一组指令&#xff0c;当这个合约或者这组指令被部署以后&#xff0c;它就不能被改变了&#xff0c;并会自动执行&#xff0c;每个人都可以看到合约里面的条款。更深层次的理解就是&#xff1a;这些代码会…...

如何查看dll文件内导出函数名称

一 使用VS自带工具 进入VS开发环境&#xff0c;然后Tools -> Visual studio 2017 Command Prompt&#xff0c;打开兼容工具命令提示符&#xff0c; 如果工具 目录下没有命令行提示&#xff0c;可以从开始菜单找到VS的命令行提示符。 cd到dll所在目录&#xff0c;输入命令…...

学习笔记|串口通信的基础知识|同步/异步|RS232|常见的串口软件的参数|STC32G单片机视频开发教程(冲哥)|第二十集:串口通信基础

目录 1.串口通信的基础知识串口通信(Serial Communication)同步/异步&#xff1f;全双工&#xff1f;常见的串口软件的参数 2.STC32的串口通信实现原理引脚选择&#xff1a;实现分时复用模式选择串口1模式1&#xff0c;模式1波特率计算公式 3.串口通信代码实现编写串口1通信程序…...