java并发编程六 ReentrantLock,锁的活跃性
多把锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)
例子
class BigRoom {private final Object studyRoom = new Object();private final Object bedRoom = new Object();public void sleep() {synchronized (bedRoom) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (studyRoom) {log.debug("study 1 小时");Sleeper.sleep(1);}}}
将锁的粒度细分
- 好处,是可以增强并发度
- 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
活跃性
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,
接下来想获取 A对象 的锁 例:
Object A = new Object();Object B = new Object();Thread t1 = new Thread(() -> {synchronized (A) {log.debug("lock A");sleep(1);synchronized (B) {log.debug("lock B");log.debug("操作...");}}}, "t1");Thread t2 = new Thread(() -> {synchronized (B) {log.debug("lock B");sleep(0.5);synchronized (A) {log.debug("lock A");
log.debug("操作...");}}}, "t2");t1.start();t2.start();
结果
12:22:06.962 [t2] c.TestDeadLock - lock B
12:22:06.962 [t1] c.TestDeadLock - lock A
定位死锁
- 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:
cmd > jpsPicked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-812320 Jps22816 KotlinCompileDaemon33200 TestDeadLock // JVM 进程
11508 Main28468 Launcher
cmd > jstack 33200Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-82018-12-29 05:51:40Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode):"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003525000 nid=0x2f60 waiting on condition
[0x0000000000000000]java.lang.Thread.State: RUNNABLE"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry
[0x000000001f54f000]java.lang.Thread.State: BLOCKED (on object monitor)at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)- waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)- locked <0x000000076b5bf1d0> (a java.lang.Object)at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)"Thread-0" #11 prio=5 os_prio=0 tid=0x000000001eb68800 nid=0x1b28 waiting for monitor entry
[0x000000001f44f000]java.lang.Thread.State: BLOCKED (on object monitor)at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)- waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)- locked <0x000000076b5bf1c0> (a java.lang.Object)at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)// 略去部分输出
Found one Java-level deadlock:============================="Thread-1":waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),which is held by "Thread-0""Thread-0":waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),which is held by "Thread-1"Java stack information for the threads listed above:==================================================="Thread-1":at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)- waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)- locked <0x000000076b5bf1d0> (a java.lang.Object)at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)"Thread-0":at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)- waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)- locked <0x000000076b5bf1c0> (a java.lang.Object)at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)at java.lang.Thread.run(Thread.java:745)Found 1 deadlock.
- 避免死锁要注意加锁顺序
- 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
public class TestLiveLock {static volatile int count = 10;
static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环
while (count > 0) {sleep(0.2);count--;log.debug("count: {}", count);}},
"t1").start();new Thread(() -> {// 期望超过 20 退出循环
while (count < 20) {sleep(0.2);count++;log.debug("count: {}", count);}},"t2").start()},
}
饥饿
饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不
易演示,讲读写锁时会涉及饥饿问题
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题

顺序加锁的解决方案

ReentrantLock
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与 synchronized 一样,都支持可重入
基本语法
// 获取锁
reentrantLock.lock();try {// 临界区
} finally {// 释放锁
reentrantLock.unlock();}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡
可打断
一个线程在等待锁的过程中,可以被其他线程打断而提前结束等待
ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {log.debug("启动...");try {lock.lockInterruptibly();}
catch (InterruptedException e) {e.printStackTrace();log.debug("等锁的过程中被打断");return;}try {log.debug("获得了锁");}
finally {lock.unlock();}}, "t1");lock.lock();log.debug("获得了锁");t1.start();try {sleep(1);t1.interrupt();log.debug("执行打断");} finally {lock.unlock();}
输出
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.onenewcode.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断
锁超时
如果某个线程在规定的时间内无法获取到锁,就会超时放弃.可以一定限度防止死锁。
ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {log.debug("启动...");try {if (!lock.tryLock(1, TimeUnit.SECONDS)) {log.debug("获取等待 1s 后失败,返回");return;}}
catch (InterruptedException e) {e.printStackTrace();}try {log.debug("获得了锁");}
finally {lock.unlock();}}, "t1");lock.lock();log.debug("获得了锁");t1.start();try {sleep(2);} finally {lock.unlock();}
输出
18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回
不公平锁
表示获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定能拿到锁, 有可能一直拿不到锁,所以结果不公平。
ReentrantLock 默认是不公平的
ReentrantLock lock = new ReentrantLock(false);lock.lock();for (int i = 0; i < 500; i++) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");}
finally {lock.unlock();}}, "t" + i).start();
} // 1s 之后去争抢锁
Thread.sleep(1000);new Thread(() -> {System.out.println(Thread.currentThread().getName() + " start...");lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");} finally {lock.unlock();}}, "强行插入").start();lock.unlock();
强行插入,有机会在中间输出
t39 running...
t40 running...
t41 running...
t42 running...
t43 running...
强行插入 start...
强行插入 running...
t44 running...
t45 running...
t46 running...
t47 running...
t49 running...
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
static ReentrantLock lock = new ReentrantLock();static Condition waitCigaretteQueue = lock.newCondition();static Condition waitbreakfastQueue = lock.newCondition();static volatile boolean hasCigrette = false;static volatile boolean hasBreakfast = false;public static void main(String[] args) {new Thread(() -> {try {lock.lock();while (!hasCigrette) {try {waitCigaretteQueue.await();}
catch (InterruptedException e) {e.printStackTrace();}}log.debug("等到了它的烟");}
finally {lock.unlock();}}).start();new Thread(() -> {try {lock.lock();while (!hasBreakfast) {try {waitbreakfastQueue.await();}
catch (InterruptedException e) {e.printStackTrace();}}log.debug("等到了它的早餐");}
finally {lock.unlock();}}).start();sleep(1);sendBreakfast();sleep(1);sendCigarette();}private static void sendCigarette() {lock.lock();try {log.debug("送烟来了");hasCigrette = true;waitCigaretteQueue.signal();}
finally {lock.unlock();}}private static void sendBreakfast() {lock.lock();try {log.debug("送早餐来了");hasBreakfast = true;waitbreakfastQueue.signal();}
finally {lock.unlock();}}
输出
18:52:27.680 [main] c.TestCondition - 送早餐来了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送烟来了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟
相关文章:
java并发编程六 ReentrantLock,锁的活跃性
多把锁 一间大屋子有两个功能:睡觉、学习,互不相干。 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低 解决方法是准备多个房间(多个对象锁…...
深度学习 | DRNN、BRNN、LSTM、GRU
1、深度循环神经网络 1.1、基本思想 能捕捉数据中更复杂模式并更好地处理长期依赖关系。 深度分层模型比浅层模型更有效率。 Deep RNN比传统RNN表征能力更强。 那么该如何引入深层结构呢? 传统的RNN在每个时间步的迭代都可以分为三个部分: 1.2、三种深层…...
代理模式:中间者的故事
代理模式:中间者的故事 介绍需求分析代理模式代码实现代理模式整理和用途第一种用途第二种用途第三种用途第四种用途 总结 介绍 本文引用《大话设计模式》第七章节的内容进行学习分析,仅供学习使用 需求:小明拜托自己好朋友小王给他朋友小美…...
中间件系列 - Redis入门到实战(高级篇-多级缓存)
前言 学习视频: 黑马程序员Redis入门到实战教程,深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 中间件系列 - Redis入门到实战 本内容仅用于个人学习笔记,如有侵扰,联系删除 学习目标 JVM进程缓存Lua语法入…...
是德科技E9304A功率传感器
是德科技E9304A二极管功率传感器测量频率范围为9 kHz至6 GHz的平均功率,功率范围为-60至20 dBm。该传感器非常适合甚低频(VLF)功率测量。E系列E9304A功率传感器有两个独立的测量路径,设计用于EPM系列功率计。功率计自动选择合适的功率电平路径。为了避免…...
视频格式网络地址转换视频到本地,获取封面、时长,其他格式转换成mp4
使用ffmpeg软件转换网络视频,先从官网下载对应操作系统环境的包 注意:网络地址需要是视频格式结尾,例如.mp4,.flv 等 官网地址:Download FFmpeg window包: linux包: 如果下载缓慢,下载迅雷安装使用…...
企业私有云容器化架构运维实战
什么是虚拟化: 虚拟化(Virtualization)技术最早出现在 20 世纪 60 年代的 IBM 大型机系统,在70年代的 System 370 系列中逐渐流行起来,这些机器通过一种叫虚拟机监控器(Virtual Machine Monitor,VMM&#x…...
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数(C++)
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数(C) Baumer工业相机Baumer工业相机NEOAPISDK中UserSet的技术背景代码案例分享第一步:保存相机当前参数设置UserSet_Save第二步:载入已经保存…...
STM32的以太网外设+PHY(LAN8720)使用详解(3):PHY寄存器详解
0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册1 PHY寄存器 前面介绍到,站管理接口(SMI)允许应用程序通过2线时钟和数据线访问任意PHY寄存器,同时该接口支持访问最多32个PHY,也…...
缓存和缓冲的区别
近期被这两个词汇困扰了,感觉有本质的区别,搜了一些资料,整理如下 计算机内部的几个部分图如下 缓存(cache) https://baike.baidu.com/item/%E7%BC%93%E5%AD%98 提到缓存(cache),就…...
C++高级-STL库概述
目录 一、概念 二、STL的历史背景 三、STL的版本 四、STL的主要优势...
uniapp 统一获取授权提示和48小时间隔授权
应用商店审核要求 获取权限前需要给提示,拒绝之后48小时不能给弹窗授权 项目用的是uniapp getImagePermission(v?: string, tag?: any, source?: any, proj?: any) {// proj proj || vueSelf.$proj(tag, source);let data {state: false,//是否原生授权denied…...
Halcon点云重建
dev_close_window () *点云文件数据的读取 read_object_model_3d (‘E:/1.om3’, ‘mm’, [], [], ObjectModel3D, Status) *获得点云的数据,例如高度 get_object_model_3d_params (ObjectModel3D, ‘point_coord_z’, GenParamValue) dev_open_window (0, 0, 512, …...
docker学习(二十一、network使用示例container、自定义)
文章目录 一、container应用示例1.需要共用同一个端口的服务,不适用container方式2.可用示例3.停掉共享源的容器,其他容器只有本地回环lo地址 总结 二、自定义网络应用示例默认bridge,容器间ip通信默认bridge,容器间服务名不通 自…...
【Python机器学习系列】一文带你了解机器学习中的Pipeline管道机制(理论+源码)
这是Python机器学习原创文章,我的第183篇原创文章。 一、引言 对于表格数据,一套完整的机器学习建模流程如下: 背景知识1:机器学习中的学习器 【Python机器学习系列】一文搞懂机器学习中的转换器和估计器(附案例&…...
算法基础之整数划分
整数划分 核心思想: 计数类dp 背包做法 f[i][j] 表示 取 1 – i 的物品 总容量为j的选法数量 f[i][j] f[i-1][j] f[i-1][j-v[i]] f[i-1][j-2v[i]] f[i-1][j-3v[i]] ……f[i-1][j-kv[i]] f[i][j-v[i]] f[i-1][j-v[i]] f[i-1][j-2v[i]] f[i-1][j-3v[i]] ……f[i…...
关于“Python”的核心知识点整理大全47
目录 16.1.10 错误检查 highs_lows.py highs_lows.py 16.2 制作世界人口地图:JSON 格式 16.2.1 下载世界人口数据 16.2.2 提取相关的数据 population_data.json world_population.py 16.2.3 将字符串转换为数字值 world_population.py 2world_population…...
Android 8.1 设置USB传输文件模式(MTP)
项目需求,需要在电脑端adb发送通知手机端接收指令,将USB的仅充电模式更改成传输文件(MTP)模式,便捷用户在我的电脑里操作内存文件,下面是我们的常见的修改方式 1、android12以下、android21以上是这种方式…...
模型量化 | Pytorch的模型量化基础
官方网站:Quantization — PyTorch 2.1 documentation Practical Quantization in PyTorch | PyTorch 量化简介 量化是指执行计算和存储的技术 位宽低于浮点精度的张量。量化模型 在张量上执行部分或全部操作,精度降低,而不是 全精度…...
adb和logcat常用命令
adb的作用 adb构成 client端,在电脑上,负责发送adb命令daemon守护进程adbd,在手机上,负责接收和执行adb命令server端,在电脑上,负责管理client和daemon之间的通信 adb工作原理 client端将命令发送给ser…...
10个Pixelfed实例运营成功案例:去中心化照片分享平台实战经验 [特殊字符]
10个Pixelfed实例运营成功案例:去中心化照片分享平台实战经验 🚀 【免费下载链接】pixelfed Photo Sharing. For Everyone. 项目地址: https://gitcode.com/GitHub_Trending/pi/pixelfed Pixelfed是一个开源的去中心化照片分享平台,基…...
主流开源License深度解析:从BSD到CC的适用场景与商业考量
1. 开源许可证的本质与核心价值 第一次接触开源许可证时,我和大多数人一样困惑:为什么明明是我的代码,却需要别人来告诉我怎么使用?后来在参与多个开源项目后才发现,许可证就像代码世界的交通规则,它不是为…...
Marker:突破PDF转换瓶颈的革新性文档处理工具
Marker:突破PDF转换瓶颈的革新性文档处理工具 【免费下载链接】marker 一个高效、准确的工具,能够将 PDF 和图像快速转换为 Markdown、JSON 和 HTML 格式,支持多语言和复杂布局处理,可选集成 LLM 提升精度,适用于学术文…...
NcmpGui:解锁网易云音乐NCM格式的终极桌面解决方案
NcmpGui:解锁网易云音乐NCM格式的终极桌面解决方案 【免费下载链接】ncmppGui 一个使用C编写的转换ncm文件的GUI工具 项目地址: https://gitcode.com/gh_mirrors/nc/ncmppGui 你是否曾因网易云音乐的NCM格式文件无法在其他播放器上正常播放而感到困扰&#x…...
掌握Argos Translate:离线翻译与隐私保护实战指南
掌握Argos Translate:离线翻译与隐私保护实战指南 【免费下载链接】argos-translate Open-source offline translation library written in Python 项目地址: https://gitcode.com/GitHub_Trending/ar/argos-translate 在当今数据隐私日益受到重视的时代&…...
Qwen2.5-7B-Instruct升级体验:从1.5B到7B,感受旗舰模型的能力跃升
Qwen2.5-7B-Instruct升级体验:从1.5B到7B,感受旗舰模型的能力跃升 1. 引言:从轻量到旗舰的进化之路 作为长期关注开源大模型的技术从业者,我见证了Qwen系列模型的快速迭代。从最初的1.5B轻量版到如今的7B旗舰版,Qwen…...
IntelliJ插件开发实战:5分钟搞定Action类库配置(附完整代码示例)
IntelliJ插件开发实战:5分钟搞定Action类库配置(附完整代码示例) 如果你刚接触IntelliJ插件开发,可能会被各种概念和配置搞得晕头转向。Action作为插件开发中最基础也最核心的组件之一,掌握它的使用方法是开发交互式功…...
OpenVoice语音合成技术全解析:从痛点突破到多场景落地实践
OpenVoice语音合成技术全解析:从痛点突破到多场景落地实践 【免费下载链接】OpenVoice 项目是MyShell AI开源的即时语音克隆技术OpenVoice,旨在提供一种能够快速从少量语音样本中准确复制人类声音特征,并实现多种语言及语音风格转换的解决方案…...
iStore软件中心:OpenWRT插件管理解决方案与实战指南
iStore软件中心:OpenWRT插件管理解决方案与实战指南 【免费下载链接】istore 一个 Openwrt 标准的软件中心,纯脚本实现,只依赖Openwrt标准组件。支持其它固件开发者集成到自己的固件里面。更方便入门用户搜索安装插件。The iStore is a app s…...
低资源部署DeepSeek-R1:苹果A17实测120 tokens/s推理速度
低资源部署DeepSeek-R1:苹果A17实测120 tokens/s推理速度 1. 模型概述 DeepSeek-R1-Distill-Qwen-1.5B是DeepSeek团队基于80万条R1推理链样本对Qwen-1.5B进行知识蒸馏得到的轻量级模型。这款"小钢炮"模型仅1.5B参数却能达到7B级模型的推理能力ÿ…...
