JUC并发编程03——LockSupport与线程中断

一.线程中断机制

假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。

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

通过一个volatile变量实现

在多线程编程中,可见性是指当一个线程修改了共享变量的值时,其他线程能够立即看到这个修改。在 Java 中,由于线程之间存在本地缓存,为了确保可见性,我们可以使用 volatile 关键字。

使用 volatile 关键字能够告诉 JVM 不要对这个变量进行本地缓存优化,而是每次都从主内存中读取变量的值。这样,当一个线程修改了 isStop 的值时,其他线程能够立即看到这个修改,确保了可见性。

使用原子变量类

通过Thread类自带的中断API方法实现

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

Java提供了一种用于停止线程的协商机制——中断。 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

  1. 若要中断一个线程,你需要手动调用该线程的 interrupt 方法,该方法也仅仅是将线程对象的中断标识设成 true,并不是真正立刻停止线程
  2. 接着你需要自己写代码不断地检测当前线程的标识位,如果为 true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。

Thread类定义了如下关于中断的方法:

1.2Thread类的三大API说明

实例方法interrupt(),没有返回值

当对一个线程,调用 interrupt() 时:

  1. 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
  2. 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么当前线程将立即退出被阻塞状态,并抛出一个InterruptedException异常,并且会清除它的中断状态,即false。
  3. 中断一个不活动的线程不会产生任何影响。

实例方法isInterrupted(),返回布尔值

测试此线程是否已中断。这个实例方法的底层调用了一个native方法,传入了一个布尔值,而这个值就是 是否清除中断标识位,false表示不清除,true表示清除(即将线程的中断标识位清除重新设置为false)。

静态方法 interrupted(),返回布尔值

Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:

  1. 返回当前线程的中断状态   
  2. 将当前线程的中断状态设为 false

二.线程之间的通信(等待唤醒机制)

方法一:Object类中的wait和notifyAll方法

需要使用synchronized关键字

  • notify唤醒队列中第一个等待线程(等待时间最长的线程),使其从wait()方法返回,而返回的前提时该线程获取到对象的锁。
  • notifyAll:通知所有等待在该对象上的线程。notify()/notifyAll() 只能唤醒等待在同一把锁上的线程
  • wait:调用此方法的线程进入阻塞等待状态,并且会被加入到一个等待队列,只有等待另外线程的通知或者被中断才会返回,调用wait方法会释放对象的锁

均是Object的方法,均只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStageException

//第一步 创建资源类,定义属性和操作方法
class Share1 {//初始值private int number = 0;//+1的方法public synchronized void incr() throws InterruptedException {//第二步 判断 干活 通知if (number != 0) { //判断number值是否是0,如果不是0,等待this.wait(); //在哪里睡,就在哪里醒}//如果number值是0,就+1操作number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}//-1的方法public synchronized void decr() throws InterruptedException {//判断if (number != 1) {this.wait();}//干活number--;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}
}public class ThreadDemo1 {//第三步 创建多个线程,调用资源类的操作方法public static void main(String[] args) {Share1 share = new Share1();//创建线程new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.incr(); //+1} catch (InterruptedException e) {e.printStackTrace();}}}, "AA").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.decr(); //-1} catch (InterruptedException e) {e.printStackTrace();}}}, "BB").start();}
}

虚假唤醒问题

上面的例子中是两个线程,我此时再创建一个线程 cc

接下来我们来分析一下这段代码为什么会出现负数的问题。

  1. 假设某一时刻,number 为 0 ,B、C两个消费者线程按顺序(因为加锁的缘故)调用 decrement 都发现 number 为 0,就都会调用 wait 方式进行释放锁进行等待;
  2. 然后线程A也调用 increment,判断是0,不满足调用wait条件,然后将 number 加成1之后,调用notifyAll方法同时唤醒B、C线程,A执行完代码,释放了锁;
  3. B、C被唤醒之后,假设B抢到锁,C没抢到,C继续阻塞,B从wait方法那继续往下走,将number 减1,此时number 变为 0
  4. B执行完释放了锁之后C这时抢到了锁,也从wait方法那继续执行代码,然后也将number 减1,这下出现问题了,线程B减完之后就是0了,线程C又将number=0减1,那不就变成-1了,所以这就产生的负数的情况。

虚假唤醒就是由于把所有线程都唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功,对于不应该被唤醒的线程而言,便是虚假唤醒。

解决方法

很简单,在等待方执行的逻辑中,一定要用while循环来判断等待条件。

因为执行notify/notifyAll方法时只是让等待线程从wait方法返回,而非重新进入临界区

方法二:Condition类中的await和signalAll方法

需要使用Lock锁

在等待方执行的逻辑中,一定要用while循环来判断等待条件。

//第一步 创建资源类,定义属性和操作方法
class Share2 {private int number = 0;//创建Lockprivate Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();//+1public void incr() throws InterruptedException {//上锁lock.lock();try {//判断while (number != 0) {condition.await();}//干活number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知condition.signalAll();} finally {//解锁lock.unlock();}}//-1public void decr() throws InterruptedException {lock.lock();try {while (number != 1) {condition.await();}number--;System.out.println(Thread.currentThread().getName() + " :: " + number);condition.signalAll();} finally {lock.unlock();}}
}public class ThreadDemo2 {public static void main(String[] args) {Share2 share = new Share2();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}}, "AA").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}}, "BB").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}}, "CC").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}}, "DD").start();}}

Condition是一个接口,可以使用 lock.newCondition() 来创建实例,Condition的方法如下:

均只能在 lock锁块 中使用,否则会抛出异常IIIegalMonitorStageException

方法三:LockSupport类中的park等待和unpark唤醒

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。可以把许可看成是一种 (0,1) 信号量(Semaphore),但与 信号量(Semaphore)不同的是,许可的累加上限是1

  • park() /park(Object blocker) :如果有凭证,则会直接消耗掉这个凭证然后正常退出;如果无凭证,就必须阻塞等待凭证可用。
  • unpark(Thread thread) :如果给定线程尚不可用,则为其提供许可。但凭证最多只能有1个,累加无效。

优点:

  • 不需要获取锁: LockSupport的阻塞和唤醒不需要先获得锁。传统的synchronized和Lock都是基于锁的,线程必须先获得锁才能调用相应的阻塞或唤醒方法。而LockSupport不依赖于任何锁,可以在任意时刻调用。
  • 不会抛出异常: LockSupport的阻塞和唤醒操作不会抛出中断异常,因此避免了因为中断而引入的异常处理逻辑。在传统的wait()和await()方法中,线程在等待时可能会被中断,需要捕获InterruptedException,而LockSupport避免了这一点。

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

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

相关文章

Android--Jetpack--Databinding源码解析

慢品人间烟火色&#xff0c;闲观万事岁月长 一&#xff0c;基本使用 关于databinding的基本使用请看之前的文章 Android--Jetpack--Databinding详解-CSDN博客 二&#xff0c;xml布局解析 分析源码呢&#xff0c;主要就是从两方面入手&#xff0c;一个是使用&#xff0c;一个…

D28|买卖股票的最佳时机+跳跃游戏

122.买卖股票的最佳时机 II 初始思路&#xff1a; 这道题解题的时候比较像在找规律&#xff0c;发现只要计算这个过程中的两数之差然后相加即可。 题解复盘&#xff1a; 可以更加清晰的发现如何从题意中获得贪心的思路。 如何贪心&#xff1a;局部最优&#xff1a;收集每天的…

mybatis多表映射-延迟加载,延迟加载的前提条件是:分步查询

1、建库建表 create database mybatis-example; use mybatis-example; create table t_book (bid varchar(20) primary key,bname varchar(20),stuid varchar(20) ); insert into t_book values(b001,Java,s001); insert into t_book values(b002,Python,s002); insert into …

2024美赛备战2--模型建立(*****必看****)

建模 美赛涉及的建模知识范围非常广且深&#xff0c;纵观美赛真题不难发现&#xff0c;很多的模型 都是读研或者读博的时候才会真正深入开始研究&#xff0c;因此&#xff0c;对于做建模的同学来说&#xff0c; 是无法在赛前吃透大量模型的。推荐本科生分两个步骤去有效准备比赛…

SpringBootWeb请求响应之前言及状态码的详细解析

SpringBootWeb请求响应 前言 在上一次的课程中&#xff0c;我们开发了springbootweb的入门程序。 基于SpringBoot的方式开发一个web应用&#xff0c;浏览器发起请求 /hello 后 &#xff0c;给浏览器返回字符串 “Hello World ~”。 其实呢&#xff0c;是我们在浏览器发起请求…

外包干了3个月,技术退步明显。。。

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

搜集怎么绘制三维曲线和曲面?

1、针对函数对象是单一变量、两个函数的情况。用plot3函数&#xff1b;&#xff08;三维曲线&#xff09; 看一下matlab官方的例子&#xff1a; t 0:pi/50:10*pi; st sin(t); ct cos(t); plot3(st,ct,t) 绘制出来的曲线&#xff1a; 几个比较关键的点&#xff1a; &…

NV040D语音芯片应用于取暖桌:智能语音提高用户体验

科技与生活的结合&#xff0c;是科技发展的展示。天气的降温&#xff0c;取暖桌越来越取得用户的心&#xff0c;时至今日传统的取暖桌已经没有办法满足用户的需求&#xff0c;智能语音取暖桌给用户的生活带来了不一样的体验。 NV040D语音芯片是一款性能稳定的芯片&#xff0c;拥…

客户案例:EDLP在央国企邮件数据合规中的价值与优势

客户背景 某机械制造企业&#xff0c;作为动力设备领域的领军企业&#xff0c;专门从事动力设备的研发、制造与销售。凭借丰富的经验与卓越的技术实力&#xff0c;该企业致力于深度研究动力设备的核心技术&#xff0c;为客户提供高效且可靠的解决方案。 客户需求 作为企业健康…

什么是特征图?

在卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;特征图是在传递给卷积层的图像上发生卷积操作后卷积层的输出。 特征图是如何形成的&#xff1f; 在上面的插图中&#xff0c;我们可以看到特征图是如何从提供的输入图像中形成的。 要发送到卷积层的图像是一个包含像…

.NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(测试篇)

WebAppDbTest 项目测试 测试工具 ltt介绍安装使用方式1、Drill2、Hammer3、Nailgun 测试主机规格配置CRUD 性能测试对比1、ltt 工具测试1.1、AddSingle 单条数据添加1.2、AddBulk 批量数据&#xff08;1000&#xff09;条添加1.3、GetSingle 单条数据查询1.4、GetAll 多条&…

文件误删危机!同事操作失误,老板情急之下该如何处理?

近期&#xff0c;一则企业员工误删数据事件引发热议。 &#xff08;截图源自网络&#xff09; 起因是某公司员工被公司辞退后&#xff0c;已经做完交接就离开了公司。而3天后&#xff0c;新来的员工不小心把这位同事原本办公电脑里的资料给删除了&#xff0c;且由于没有进行数…