【JUC】LockSupport线程等待唤醒

文章目录

  • LockSupport
    • 线程等待唤醒机制
      • 三种让线程等待和唤醒的方法
      • Object类中的wait和notify方法实现线程等待和唤醒
      • Condition接口中的await和signal方法实现线程的等待和唤醒
      • 上述两种方法使用限制条件
      • LockSupport类中的park等待和unpark唤醒
        • LockSupport 是什么
        • 主要方法
        • 代码测试
    • 面试题
      • 为什么LockSupport可以突破wait/notify的原有调用顺序?
      • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
  • 文章说明

LockSupport

线程等待唤醒机制

三种让线程等待和唤醒的方法

  • 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 方式二:使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • 方式三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

在这里插入图片描述

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

  • wait和notify方法必须要在同步代码块或者同步方法里面(就是必须持有锁才能使用这两个方法),且成对出现使用
  • 先wait再notify才是合法的
  • 调用wait会交出我锁的控制权,让别人去争抢
public class LockSupportDemo {public static void main(String[] args) {Object objectLock = new Object();new Thread(() -> {synchronized (objectLock) {System.out.println(Thread.currentThread().getName() + "\t -----------come in");try {// 调用wait()方法使当前线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒它。objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");}}, "t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {synchronized (objectLock) {// 调用notify()方法唤醒因调用wait()而处于等待状态的一个线程。objectLock.notify();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");}}, "t2").start();}
}

在这里插入图片描述

不用同步代码块

public class LockSupportDemo {public static void main(String[] args) {Object objectLock = new Object();/*** t1         -----------come in* t2         -----------发出通知* t1         -------被唤醒*/new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t -----------come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");}, "t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {objectLock.notify();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");}, "t2").start();}
}

在这里插入图片描述

wait、notify顺序反过来

public class LockSupportDemo {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() + "\t -----------come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");}}, "t1").start();new Thread(() -> {synchronized (objectLock) {objectLock.notify();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");}}, "t2").start();}
}

卡死原因:wait之后,没有人唤醒我

在这里插入图片描述

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

  • Condition中的线程等待和唤醒方法,需要先持有锁,在锁块里面才能使用
  • 一定要先await后signal
public class LockSupportDemo {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "\t -----------come in");condition.await();System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}, "t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");} finally {lock.unlock();}}, "t2").start();}
}

在这里插入图片描述

模拟没有持有锁

public class LockSupportDemo {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {// lock.lock();try {System.out.println(Thread.currentThread().getName() + "\t -----------come in");condition.await();System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");} catch (InterruptedException e) {e.printStackTrace();} finally {// lock.unlock();}}, "t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {// lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");} finally {// lock.unlock();}}, "t2").start();}
}

在这里插入图片描述

模拟没有先await后signal

public class LockSupportDemo {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {lock.lock();try {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t -----------come in");condition.await();System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}, "t1").start();new Thread(() -> {lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");} finally {lock.unlock();}}, "t2").start();}
}

发生阻塞

在这里插入图片描述

上述两种方法使用限制条件

  • 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

LockSupport类中的park等待和unpark唤醒

LockSupport 是什么
  • LockSupport是一个线程阻塞工具类

在这里插入图片描述

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()的作用分别是阻塞线程和解除阻塞线程。调用park的时候,除非有许可证,不然线程就阻塞
  • LockSupport可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法
  • LockSupport是对等待唤醒机制的一种优化。LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程最多获取一个许可,重复调用unpark也不会积累凭证
  • 该类没有构造方法,所有的方法都是静态方法

在这里插入图片描述

  • 如果许可证可用,park方法立即返回,否则被阻止;如果没有许可,调用unpark可以获取一个许可
  • 参数传0的意思:“没有通行证,永远不放行”,LockSupport时调用Unsafe中的native代码。

在这里插入图片描述

在这里插入图片描述

  • 默认没有permit许可证,所以一开始调park()当前线程就会阻塞,直到别的线程给当前线程的发放permit,pack方法才会被唤醒。
  • 调用unpark(thread)方法后,就会给thread线程发放许可证,会自动唤醒park的线程,即之前阻塞中的LockSupport.park()方法会立即返回

在这里插入图片描述

  • 形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有1个
    • 当调用park时
      • 如果有凭证,则会直接消耗掉这个凭证然后正常退出
      • 如果没有凭证,则必须阻塞等待凭证可用
    • 当调用unpark时,它会增加一个凭证,但凭证最多只能有1个,累加无效。
主要方法
  • 阻塞: Peimit许可证默认没有不能放行,所以一开始调用park()方法当前线程会阻塞,直到别的线程给当前线程发放peimit,park方法才会被唤醒。
    • park/park(Object blocker):阻塞当前线程/阻塞传入的具体线程
  • 唤醒: 调用unpack(thread)方法后 就会将thread线程的许可证peimit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回。
    • unpark(Thread thread):唤醒处于阻塞状态的指定线程
代码测试
public class LockSupportDemo {public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t -----------come in");// 等待LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");}, "t1");t1.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {// 唤醒LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");}, "t2").start();}
}

不需要在锁块中才能执行等待、唤醒

在这里插入图片描述

public class LockSupportDemo {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t -----------come in");// 等待LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");}, "t1");t1.start();new Thread(() -> {// 唤醒LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");}, "t2").start();}
}

先用unpack发许可证,再pack,还是可以唤醒。类似高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。

在这里插入图片描述

【测试许可证是否可以积累】

public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t ----come in" + System.currentTimeMillis());LockSupport.park();LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t ----被唤醒" + System.currentTimeMillis());}, "t1");t1.start();//暂停几秒钟线程//try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {LockSupport.unpark(t1);LockSupport.unpark(t1);LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t ----发出通知");}, "t2").start();
}

调用多次LockSupport.unpark(t1);也只有一个通行证,来到第二个park会被卡住

在这里插入图片描述

面试题

为什么LockSupport可以突破wait/notify的原有调用顺序?

  • 因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻。

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

  • 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会发一个凭证,而调用两次park却需要消费两个凭证,证不够,第二次不能放行。

文章说明

该文章是本人学习 尚硅谷 的学习笔记,文章中大部分内容来源于 尚硅谷 的视频尚硅谷JUC并发编程(对标阿里P6-P7),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 尚硅谷 的优质课程表示感谢。

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

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

相关文章

网易云音乐黑胶VIP会员免费领取入口直达词令是什么?

网易云音乐黑胶VIP会员免费领取是指网易云音乐VIP会员根据不同的等级尊享不同的权益,其中赠送礼品卡就是其一。不同等级的网易云音乐VIP会员可赠送的7天黑胶VIP会员张数不同,但是由于数量有限,每次更新后先领先得,我们将不定期根据…

SpringBoot3:轻松使用Jasypt实现配置文件信息加密

文章目录 前言一、概述1.1 Jasypt库简介1.2 Jasypt库的主要特点 二、开发环境三、Jasypt集成到SpringBoot33.1 引入依赖3.2 配置Jasypt3.3 加密配置文件信息3.3.1 方案一(不推荐)a.编写测试类生成加密后的配置文件信息b.运行c.修改原本的配置文件信息 3.…

vue实现电子签名、图片合成、及预览功能

业务功能:电子签名、图片合成、及预览功能 业务背景:需求说想要实现一个电子签名,然后需要提供一个预览的功能,可以查看签完名之后的完整效果。 需求探讨:后端大佬跟我说,文档我返回给你一个PDF的oss链接…

开源大模型的格式转成GGUF,并量化后使用ollama推理

https://github.com/ggerganov/llama.cpphttps://github.com/ggerganov/llama.cpp使用到的工具: llama.cpp ollama 步骤 1、下载llama.cpp,并使用make编译 2、新建conda环境,安装llama.cpp里所需的库(requirements.txt) 3、下载需要量化的模型

1. BES2700ZP概述

1. 概述 恒玄BES2700采用RTX5操作系统,配合mindmics算法或者自研算法。 RTX5相关接口可参考:RTX v5 Implementation 2. 芯片框架 2.1 内存 - 4MB 2.2 flash - 8MB

openmv 学习笔记(24电赛笔记)

模版匹配 模版匹配是一种计算机视觉技术,用于图像或者视频中查找特定的模版或者对象,查找模版可以是数字或者是物体,技术通过在目标图像中寻找与模版图像相似的区域来实现匹配。这种技术最早起源在 20世纪70年代 的图像处理领域。 使用模版匹…

《python程序语言设计》第6章14题 估算派值 类似莱布尼茨函数。但是我看不明白

这个题提供的公式我没看明白,后来在网上找到了莱布尼茨函数 c 0 for i in range(1, 902, 100):a (-1) ** (i 1)b 2 * i - 1c a / bprint(i, round(4 / c, 3))结果 #按题里的信息,但是结果不对,莱布尼茨函数到底怎么算呀。

无人机的飞行模式

无人机的飞行模式是提升飞行效率和完成特定任务的关键。现代无人机通常配备多种智能飞行模式,这些模式能够帮助飞行员高效且安全地完成飞行任务。以下是几种常见的无人机飞行模式及其应用场景的解析: 一、跟随模式 应用场景:跟随模式非常适…

【React】详解classnames工具:优化类名控制的全面指南

文章目录 一、classnames的基本用法1. 什么是classnames?2. 安装classnames3. 导入classnames4. classnames的基本示例 二、classnames的高级用法1. 动态类名2. 传递数组3. 结合字符串和对象4. 结合数组和对象 三、实际应用案例1. 根据状态切换类名2. 条件渲染和类名…

Halcon 设置处理区域AOI(用户交互,drawing_object)

主程序 * 1.加载并显示图片 ************************* read_image (Image, ./model)dev_get_window (WindowHandle) set_display_font (WindowHandle, 14, sans, true, false) dev_set_draw (margin) dev_set_line_width (3) dev_display (Image)* 读取字典文件 ************…

35.【C语言】详解函数递归

目录: 定义 作用 例子1~3 拓展学习 趣味练习 1.定义:函数自己调用自己(递推回归) int main() {main()return 0; } 这样容易死循环,导致爆栈(Stack Overflow) 所以需要设立限制条件,使执行时越来越接近条…

DOS攻击实验

实验背景 Dos 攻击是指故意的攻击网络协议实现的缺陷或直接通过野蛮手段,残忍地耗尽被攻击对象的资源,目的是让目标计算机或网络无法提供正常的服务或资源访问,使目标系统服务系统停止响应甚至崩溃。 实验设备 一个网络 net:cloud0 一台模…

顺序表算法题

在学习了顺序表专题后,了解的顺序表的结构以及相关概念后就可以来试着完成一些顺序表的算法题了,在本篇中将对三道顺序表相关的算法题进行讲解,希望能对你有所帮助,一起加油吧!!! 1.移除元素 2…

SpringBoot知识笔记

一、基本概念 1.1 特性 起步依赖 自动配置 其它特性:内嵌的Tomcat、Jetty(无需部署WAR文件),外部配置,不需要XML配置(properties/yml)。 1.2 配置文件 SpringBoot提供了多种属性配置方式 //application.properties server.port=9090 server.servlet.context-path…

Linux下Centos7中的gcc/g++

命为志存。 —— 朱熹 Linux中C/C翻译过程 1、样例介绍1、1、gcc版本过低不能编译成功1、2、编写 .cxx或.cc或.cpp代码(都是C) 2、程序的翻译过程2、1、条件编译(补充)2、2、语言历史 3、深入理解链接3、1、静态链接的使用场景 1、样例介绍 1、1、gcc版本过低不能编译成功 in…

前端自动化测试(一):揭秘自动化测试秘诀

目录 [TOC](目录)前言自动化测试 VS 手动测试测试分类何为单元测试单元测试的优缺点优点缺点 测试案例测试代码 测试函数的封装实现 expect 方法实现 test 函数结语 正文开始 , 如果觉得文章对您有帮助,请帮我三连订阅,谢谢💖&…

android(安卓)最简单明了解释版本控制之MinSdkVersion、CompileSdkVersion、TargetSdkVersion

1、先明白几个概念 (1)平台版本(Android SDK版本号) 平台版本也就是我们平时说的安卓8、安卓9、安卓10 (2)API级别(API Level) Android 平台提供的框架 API 被称作“API 级别” …

Android APK混淆处理方案分析

这里写目录标题 一、前言1.1 相关工具二、Apk 分析2.1 apk 解压文件2.2 apk 签名信息2.3 apk AndroidManifest.xml2.4 apk code三、Apk 处理3.1 添加垃圾文件3.2 AndroidManifest.xml 处理3.3 dex 混淆处理3.4 zipalign对齐3.5 apk 重新签名3.6 apk 安装测试四、总结一、前言 提…

Unity打包设置

1.Resolution and Presentation (分辨率和显示) Fullscreen Window (全屏窗口): 应用程序将以全屏窗口模式运行,但不会独占屏幕。适用于想要全屏显示但仍需访问其他窗口的情况。 Resizable Window (可调整大小的窗口): 允许用户调整应用程序窗口的大小。适用于窗口…

Selenium相对定位

测试网站:Web form 相对定位的方法: above():定位基准元素上方的元素below():定位基准元素下方的元素to_left_of():定位基准元素左侧的元素to_right_of():定位基准元素右侧的元素near() :定位基…