【Java | 多线程】LockSupport 的使用和注意事项

了解一下 LockSupport

LockSupport是一个类,位于java.util.concurrent.locks包中,提供了基本的线程同步机制。

LockSupport的主要作用是挂起和唤醒线程。它提供了两个主要的静态方法:park()unpark()

  1. park():用于挂起当前线程。如果调用park()的线程已经被unpark(),或者线程被中断,那么调用park()时线程不会阻塞。
  2. unpark(Thread thread):用于唤醒指定的线程。如果该线程在调用unpark()时已经处于挂起状态,那么它会被唤醒。如果该线程还没有进入挂起状态,那么下一次调用park()时不会阻塞。

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

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

我们知道,使用Objectwait()notify()方法,可以实现基本的线程等待和唤醒。

并发包(java.util.concurrent)下Condition对象的await()signal()方法也可以实现线程等待和唤醒。

但是,wait()notify()必须在同步块或同步方法中调用,否则会抛出IllegalMonitorStateException。类似的,调用Conditionawait()signal()方法也需要获取相关Lock对象的锁的情况下才能调用,否则会同样会抛出IllegalMonitorStateException

另外,如果我们先调用notify(),然后再调用wait()。在这种情况下,notify()被调用时没有线程在等待,所以没有线程会被唤醒,之后当线程调用wait()时,它会进入等待状态(阻塞了)。

public class WaitNotifyDemo {static Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 让 Thread A 稍后运行try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 开始");try {lock.wait();} catch (Exception e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + " 被唤醒!");}, "Thread A").start();new Thread(() -> {synchronized (lock) {lock.notify();System.out.println(Thread.currentThread().getName() + " 唤醒操作");}}, "Thread B").start();}
}

运行效果:

Thread B 唤醒操作
Thread A 开始

可以看到,线程 A 一直处于阻塞状态,等待其他线程再次调用notify()

那如果是用LockSupportpark()unpark(),就不会有上述问题。

import java.util.concurrent.locks.LockSupport;public class LockSupportDemo {// LockSupport 不用必须在同步块或同步方法中调用public static void main(String[] args) {Thread threadA = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 开始");LockSupport.park();System.out.println(Thread.currentThread().getName() + " 被唤醒!");}, "Thread A");Thread threadB = new Thread(() -> {LockSupport.unpark(threadA);System.out.println(Thread.currentThread().getName() + " 唤醒操作");}, "Thread B");threadA.start();threadB.start();}
}

即使是先唤醒后等待,使用 LockSupport 也没有问题:

import java.util.concurrent.locks.LockSupport;public class LockSupportDemo {public static void main(String[] args) {Thread threadA = new Thread(() -> {// 让 Thread A 稍后运行try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 开始");LockSupport.park();System.out.println(Thread.currentThread().getName() + " 被唤醒!");}, "Thread A");Thread threadB = new Thread(() -> {LockSupport.unpark(threadA);System.out.println(Thread.currentThread().getName() + " 唤醒操作");}, "Thread B");threadA.start();threadB.start();}
}

运行效果:

Thread B 唤醒操作
Thread A 开始
Thread A 被唤醒!

关键点

说白了,LockSupport提供了静态方法park()unpark()方法来实现阻塞线程和解除线程阻塞的过程。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。permit相当于1,0的开关。

permit的默认值为0。调用一次unpark就加1,调用一次park会消费permit,也就是将1变成0,同时park立即返回。此时如果再次调用park会变成阻塞,调用unpark就又会把permit置为1。

需要注意的是,每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。

官网是这么写的:

在这里插入图片描述

为什么在LockSupport类中,我们可以先唤醒一个线程后再让它阻塞?

这是因为LockSupport的工作原理基于许可(permit)的概念。

当我们调用unpark方法时,如果相关线程还没有许可,那么它会获得一个许可。然后,当我们在之后调用park方法时,如果该线程已经有了许可,那么它会立即消费这个许可并立即返回,而不会阻塞。因此,即使我们先唤醒线程(即先调用unpark方法),然后再让它阻塞(调用park方法),线程也不会真正阻塞,因为它已经有了一个许可可以消费。

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

这是因为LockSupport的许可(permit)不具备累加性。

无论我们调用多少次unpark方法,它只会给线程一个许可(将permit置为1)。

当我们连续两次调用park方法时,第一次调用会消费掉这个许可,然后第二次调用park方法时,由于没有可用的许可,线程会被阻塞。因此,即使我们先连续两次唤醒线程,然后再连续两次让它阻塞,线程最终还是会被阻塞。

下面的代码演示了使用 LockSupport 类唤醒了两次A线程后阻塞两次,结果A线程会阻塞:

import java.util.concurrent.locks.LockSupport;public class LockSupportDemo {public static void main(String[] args) {Thread threadA = new Thread(() -> {// 让 Thread A 稍后运行try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 开始");LockSupport.park();LockSupport.park();System.out.println(Thread.currentThread().getName() + " 被唤醒!");}, "Thread A");Thread threadB = new Thread(() -> {LockSupport.unpark(threadA);LockSupport.unpark(threadA);System.out.println(Thread.currentThread().getName() + " 唤醒操作");}, "Thread B");threadA.start();threadB.start();}
}

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

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

相关文章

thsi指针用法总结

1 c类对象中的变量和函数是分开存储的 2 所以对象共用一份成员函数,类的大小是指非静态的成员变量; this 完成链式操作 const 修饰成员函数

Kafka 3.x.x 入门到精通(02)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通(02)——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.1.1 解压文件2.1.2 安装ZooKeeper2.1.3 安装Kafka2.1.4 封装启动脚本 2.2 集群启动2.2.1 相关概念2.2.1.1 代理:Broker2.2.1.2 控制器:Controller …

【Linux 开发第一篇】如何在安装中完成自定义配置分区

安装配置自定义配置分区 在安装Centos的过程中,我们可以在安装位置部分手动配置分区 选择我要配置分区,点击完成: 我们自动分区分为三个分区:boot分区(引导分区),swap(交换分区&…

云备份项目--项目介绍

📟作者主页:慢热的陕西人 🌴专栏链接:C云备份项目 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 主要内容项目一些详细信息的介绍 文章目录 云备份项目1.项目介绍…

WPS表格,怎样保留每个人的最近日期的那一行数据?

方法很多,这里演示使用排序删除重复项 来完成。具体操作如下: 1. 选中数据区域中任意一个单元格,注意要么全选数据区域,要么只选一个单元格 2. 点击数据选项卡,排序,自定义排序, 在弹出对话框…

告别互信息:跨模态人员重新识别的变分蒸馏

Farewell to Mutual Information: Variational Distillation for Cross-Modal Person Re-Identification 摘要: 信息瓶颈 (IB) 通过在最小化冗余的同时保留与预测标签相关的所有信息,为表示学习提供了信息论原理。尽管 IB 原理已应用于广泛的应用&…

简述MASM宏汇编

Hello , 我是小恒不会java。今天写写x86相关底层的东西 寄存器 8086由BIU和EU组成 8088/8086寄存器有14个。8通用,4段,1指针,1标志 8个通用寄存器:这些寄存器可以用来存储任意类型的数据,包括整数、地址等。8086有8个…

【Java--数据结构】提升数据处理速度!深入理解Java中的顺序表机制

欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 目录 两种创建顺序表的方法及区别 认识ArrayList的构造方法 不带参数的构造方法 带参数的构造方法 利用Collection 构造方法 举例 ArrayList 常用方法演示 add addAll remo…

Linux进程详解三:进程状态

文章目录 进程状态Linux下的进程状态运行态-R阻塞态浅度休眠-S深度睡眠-D暂停状态-T暂停状态-t 终止态僵尸-Z死亡-X 孤儿进程 进程状态 进程的状态,本质上就是一个整型变量,在task_struct中的一个整型变量。 状态的存在决定了你的后续行为动作。 Linu…

直接用表征还是润色改写?LLM用于文生图prompt语义增强的两种范式

直接用表征还是润色改写?LLM用于文生图prompt语义增强的两种范式 导语 目前的文生图模型大多数都是使用 CLIP text encoder 作为 prompt 文本编码器。众所周知,由于训练数据是从网络上爬取的简单图文对,CLIP 只能理解简单语义,而…

拿捏 顺序表(1)

目录 1. 顺序表的分类2. 顺序表实现3. 顺序表实现完整代码4. 总结 前言: 一天xxx想存储一组数据, 并且能够轻松的实现删除和增加, 此时数组大胆站出, 但是每次都需要遍历一遍数组, 来确定已经存储的元素个数, 太麻烦了, 于是迎来了顺序表不屑的调侃: 数组你不行啊… 顺序表是一…

第二期书生浦语大模型训练营第四次笔记

大模型微调技术 大模型微调是一种通过在预训练模型的基础上,有针对性地微调部分参数以适应特定任务需求的方法。 微调预训练模型的方法 微调所有层:将预训练模型的所有层都参与微调,以适应新的任务。 微调顶层:只微调预训练模型…

oracle 12c+ max_string_size参数

一个客户的数据库版本是19.3,在做数据库复制的时候,目标端报错了,查看了一下问题发现表的字段长度有不对,在12c以前我们都知道varchar的长度最大是4000,但是客户这里居然有32767: 把客户的建表语句弄出来,放到我的一个19c的测试环境进行测试: 发现报错了: 这里报错很明显了,是M…

学习c语音的自我感受

因为是自学,所以走过不少弯路。去年,受知乎“python性能弱”风潮的影响,学过go,rust。 在学习这些新语言的时候,由衷感受到,或是本身侧重方向的原因(如go侧重服务器),或是语言太新不…

uniapp——组件多颜色模块展示、气泡框

一、自定义颜色&#xff1a; 样式 代码 <template><view class"content"><!-- 右上角 --><view class"coverStatus" :class"[itemClass, positionClass,cornerClass,sanJiaoCss,sanJiaoCss2]":style"dynamicStyle&q…

python getsize如何使用

第一步&#xff0c;点击键盘 winr&#xff0c;打开运行窗口&#xff1b;在窗口中输入“cmd"&#xff0c;点击确定&#xff0c;打开windows命令行窗口。 第二步&#xff0c;在windows命令行窗口中&#xff0c;输入“python”&#xff0c;进入python交互窗口。 第三步&#…

笔记 | 嵌入式系统概论

1 嵌入式系统简介 1.1 嵌入式系统的定义 根据美国电气与电子工程师学会&#xff08;IEEE&#xff1a;Institute of Electrical and Electronics Engineers )的定义&#xff0c;嵌入式系统是用于控制、监视或辅助操作机器和设备的装置(原文: devices used to control, monitor…

WiFi、Ethenet、4G优先级切换

1、多网卡情况下如何调整优先级方案 按照目前公司前辈给出的方案&#xff0c;调整优先级的手段有两种&#xff1a; <1>.删除默认路由--route del 的方法 <2>.ifmetric源码提供的修改路由表的mteric的值来设置路由的优先级&#xff0c;metric越小优先级越高。 应…

ANSYS Help 的使用

ANSYS 帮助文档是相当实用且重要的第一手资料&#xff0c;90% 以上的纯操作问题都可以在帮助文档找到相关的解释。 点击开始菜单的 ANSYS Help即可打开帮助文档 帮助文档有两种打开方式&#xff1a; 基于帮助文档的安装包安装于本地后&#xff0c;可直接启动帮助程序没有安装…

Spring-IOC之组件扫描

版本 Spring Framework 6.0.9​ 1. 前言 通过自动扫描&#xff0c;Spring 会自动从扫描指定的包及其子包下的所有类&#xff0c;并根据类上的特定注解将该类装配到容器中&#xff0c;而无需在 XML 配置文件或 Java 配置类中逐一声明每一个 Bean。 支持的注解 Spring 支持一系…