多线程——死锁
死锁
在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。
我们只能通过中止并重启的方式来让程序重新执行。
这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!
死锁的原因:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
锁顺序死锁
线程1调用
leftRight()方法,得到left锁
同时线程2调用rightLeft()方法,得到right锁线程1和线程2都继续执行,此时线程1需要right锁才能继续往下执行。此时线程2需要left锁才能继续往下执行。
线程1的left锁并没有释放,线程2的right锁也没有释放。
所以他们都只能等待,而这种等待是无期限的–>永久等待–>死锁
class Demo implements Runnable{private final Object left = new Object(); private final Object right = new Object();@Overridepublic void run() {leftRight();rightLeft();}public void leftRight() {synchronized (left) {System.out.println("线程" + Thread.currentThread().getName() + "拿到left,还需要right");synchronized (right) {System.out.println("线程" + Thread.currentThread().getName() + "拿到left和right");}}}public void rightLeft() {synchronized (right) {System.out.println("线程" + Thread.currentThread().getName() + "拿到right,还需要left");synchronized (left) {System.out.println("线程" + Thread.currentThread().getName() + "拿到right和left");}}}
}
动态锁顺序死锁
代码释义:获取源账户与目标账户,判断余额充足后进行加减操作。
死锁现象:线程1源账户a,目标账户b。线程2源账户b,目标账户a。
public class ThreadTest {public static void main(String[] args) {Test test = new Test();new Thread(test, "1").start();new Thread(test, "2").start();new Thread(test, "3").start();new Thread(test, "4").start();}
}class Test implements Runnable {Account a = new Account("A", 1000);Account b = new Account("B", 1000);@Overridepublic void run() {transferMoney(a, b, 100);transferMoney(b, a, 100);}public void transferMoney(Account fromAccount, Account toAccount, double money) {synchronized (fromAccount) {System.out.println("线程" + Thread.currentThread().getName() + "获得账户" + fromAccount.getName());synchronized (toAccount) {System.out.println("线程" + Thread.currentThread().getName() + "获得账户" + toAccount.getName());if (fromAccount.getMoney() < money) {System.out.println("余额不足");} else {fromAccount.setMoney(fromAccount.getMoney() - money);toAccount.setMoney(toAccount.getMoney() + money);System.out.println("转账后:" + fromAccount.getName() + "的余额:" +fromAccount.getMoney());System.out.println("转账后:" + toAccount.getName() + "的余额:" + toAccount.getMoney());}}}}
}
@Data
class Account {public Account(String name, double money) {this.name = name;this.money = money;}private String name;private double money;
}
协作对象之间发生死锁
getImage()和setLocation(Point location)都需要获取两个锁,且在操作途中是没有释放锁的。
这就是隐式获取两个锁(对象之间协作)很容易造成死锁
public class CooperatingDeadlock {class Taxi {private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}// setLocation 需要Taxi内置锁public synchronized void setLocation(Point location) {this.location = location;if (location.equals(destination))// 调用notifyAvailable()需要Dispatcher内置锁dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}class Dispatcher {private final Set<Taxi> taxis;private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}// 调用getImage()需要Dispatcher内置锁public synchronized Image getImage() {Image image = new Image();for (Taxi t : availableTaxis)// 调用getLocation()需要Taxi内置锁image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}
}
避免死锁的方法
避免死锁可以概括成三种方法:
- 固定加锁的顺序(针对锁顺序死锁)
- 开放调用(针对对象之间协作造成的死锁)
- 使用定时锁–>
tryLock()如果等待获取锁时间超时,则抛出异常而不是一直等待
固定锁顺序避免死锁
得到对应的hash值来固定加锁的顺序,这样不会出现死锁。
public class OrderLock {private static final Object tieLock = new Object();public void transferMoney(final Account fromAccount, final Account toAccount, final DollarAmount amount)throws InsufficientFundsException {class Helper {public void transfer() throws InsufficientFundsException {if (fromAccount.getBalance().compareTo(amount) < 0)throw new InsufficientFundsException();else {fromAccount.debit(amount);toAccount.credit(amount);}}}int fromHash = System.identityHashCode(fromAccount);int toHash = System.identityHashCode(toAccount);if (fromHash < toHash) {synchronized (fromAccount) {synchronized (toAccount) {new Helper().transfer();}}} else if (fromHash > toHash) {synchronized (toAccount) {synchronized (fromAccount) {new Helper().transfer();}}} else {synchronized (tieLock) {synchronized (fromAccount) {synchronized (toAccount) {new Helper().transfer();}}}}}
}
开放调用避免死锁
在协作对象之间发生死锁的例子中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法。
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。同步代码块最好仅被用于保护那些涉及共享状态的操作。
class CooperatingLock {@ThreadSafeclass Taxi {@GuardedBy("this")private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}public void setLocation(Point location) {boolean reachedDestination;synchronized (this) {this.location = location;reachedDestination = location.equals(destination);}if (reachedDestination)dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}@ThreadSafeclass Dispatcher {@GuardedBy("this")private final Set<Taxi> taxis;@GuardedBy("this")private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}public Image getImage() {Set<Taxi> copy;synchronized (this) {copy = new HashSet<Taxi>(availableTaxis);}Image image = new Image();for (Taxi t : copy)image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}
}
使用定时锁
使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超时的时候,tryLock()不会一直等待,而是返回错误信息。
使用tryLock()能够有效避免死锁问题。tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
public class tryLock {public static void main(String[] args) {System.out.println("开始");final Lock lock = new ReentrantLock();new Thread() {@Overridepublic void run() {String tName = Thread.currentThread().getName();if (lock.tryLock()) {System.out.println(tName + "获取到锁!");} else {System.out.println(tName + "获取不到锁!");return;}try {for (int i = 0; i < 5; i++) {System.out.println(tName + ":" + i);}Thread.sleep(5000);} catch (Exception e) {System.out.println(tName + "出错了!!!");} finally {System.out.println(tName + "释放锁!!");lock.unlock();}}}.start();new Thread() {@Overridepublic void run() {String tName = Thread.currentThread().getName();if (lock.tryLock()) {System.out.println(tName + "获取到锁!");} else {System.out.println(tName + "获取不到锁!");return;}try {for (int i = 0; i < 5; i++) {System.out.println(tName + ":" + i);}} catch (Exception e) {System.out.println(tName + "出错了!!!");} finally {System.out.println(tName + "释放锁!!");lock.unlock();}}}.start();System.out.println("结束");}
}
总结
发生死锁的原因主要由于:
-
线程之间交错执行
解决:以固定的顺序加锁
-
执行某方法时就需要持有锁,且不释放
解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
-
永久等待
解决:使用
tryLock()定时锁,超时则返回错误信息
相关文章:
多线程——死锁
死锁 在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。 我们只能通过中止并重启的方式来让程序重新执行。 这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生! 死锁的原因…...
链路追踪可视化利器之火焰图
随着现代化技术的发展,为了能够保证 IT 系统的稳定性、高扩容性,企业往往采用分布式的方式来构建 IT 系统。但也正因为如此,IT 系统中涉及到的服务和组件可能被分布在不同的服务器、数据中心甚至不同的地理位置,这导致应用发生故障…...
C语言 ——— 条件编译指令实际用途
目录 前言 头文件被包含的方式 嵌套文件包含 使用条件编译指令规避头文件多次包含 还有一个编译指令,同样能做到以上功能 前言 条件编译指令多用于对头文件的定义和判断以及删除 头文件被包含的方式 本地文件包含(也就是自己创建的头文件ÿ…...
备战软考Day01-计算机系统
1.数值及其转化 1.数值转化(十进制) 2.十进制推广 3.进制转化 4.数据表示 1.原码 2.反码 3.补码 4.移码 5.定点数 就是小数点的位置固定不变的数。小数点的位置通常有两种约定方式:定点整数(纯整数,小数点在最低有效数值位之后…...
从C语言过渡到C++
📔个人主页📚:秋邱-CSDN博客☀️专属专栏✨:C 🏅往期回顾🏆:单链表实现:从理论到代码-CSDN博客🌟其他专栏🌟:C语言_秋邱的博客-CSDN博客 目录 …...
Docker 的安装和使用
参考资料: 通俗易懂了解什么是docker?Docker 教程 | 菜鸟教程Ubuntu 22.04 安装 DockerDocker 超详细基础教程WSL2 支持 systemctl 命令systemd 和 systemctl 是什么?使用正确的命令重启 WSL 子系统Ubuntu 修改源镜像方法Docker 中出现 ‘/etc/resolv.…...
鸿蒙轻内核A核源码分析系列七 进程管理 (2)
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 轻内核A核源码分析系列一 数据结构-双向循环链表 轻内核A核源码分析系列二 数据结构-位图操作 轻内核A核源码分析系列三 物理内存(1࿰…...
关于TypeScript使用讲解
TypeScript讲解 安装环境 1.安装node js 配置环境变量 2.在终端中 运行 npm i -g typescript typescript: 用于编译ts代码 提供了 tsc命令 实现了将 TS>>>> JS转换 验证: tsc -v 编译并运行 TS代码 1.创建ts文件(TS文件为后缀名的文件࿰…...
C语言 | Leetcode C语言题解之第393题UTF-8编码验证
题目: 题解: static const int MASK1 1 << 7; static const int MASK2 (1 << 7) (1 << 6);bool isValid(int num) {return (num & MASK2) MASK1; }int getBytes(int num) {if ((num & MASK1) 0) {return 1;}int n 0;in…...
Netty权威指南:Netty总结-编解码与序列化
第四章 TCP粘包/拆包问题 4.1 TCP 粘包/拆包 TCP是流协议,也就是没有界限的的一串数据,底层并不知道上层业务数据的具体含义,也就是说一个完整的包可能会被拆分成多个包进行发送,也可能把几个小包封装成一个大的数据包发送。这就…...
FIDAVL:基于视觉语言模型的假图像检测与归因 !
FIDAVL:基于视觉语言模型的假图像检测与归因 ! 这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 作者提出了FIDAVL:使用视觉语言模型进行虚假图像检测。FIDAVL是一…...
如何通过海外云手机提升运营效率
随着技术的不断进步,市场上出现了越来越多的提高跨国电商运营效率的应用,海外云手机就是其中一个。海外云手机的优势体现在多个方面,那么如何通过使用海外云手机来提升运营效率?可以从以下几个方面了解。 首先,海外云手…...
数据库4个范式的说明
在数据库设计中,范式(Normal Form)用于消除冗余和异常,确保数据一致性。以下是第一范式、第二范式、第三范式和BCNF(Boyce-Codd Normal Form,即第四范式)的示例说明: 1. 第一范式&a…...
Excel怎么截图?快速捕捉工作表的多种方法
大家好,这里是效率办公指南! 📸 在日常工作中,我们经常需要对Excel工作表进行截图,无论是为了记录数据、制作演示还是进行数据对比。今天,我们就来学习几种在Excel中截图的方法以及它们的快捷键。 一、使…...
MyBatis动态SQL标签总结、开发手册、高阶用法(动态SQL、OGNL、批量操作、片段重用、 SQL 组合、 执行优化、嵌套查询与延迟加载)
MyBatis提供了一个非常强大的动态SQL功能,它使用了一组XML标签来帮助我们根据不同条件生成动态SQL。动态SQL的设计让开发者可以根据业务需求,灵活地构建SQL查询语句。以下是MyBatis动态SQL标签的总结。 动态SQL标签说明特点<if>条件判断语句&…...
出处不详 取数游戏
目录 取数游戏题目描述背景输入输出数据范围 题解解法优化 打赏 取数游戏 题目描述 背景 两人将 n n n个正整数围成一个圆环,规则如下: 第一名玩家随意选取数字;第二名玩家从与第一名玩家相邻的两个数字中选择一个;而后依次在…...
拉取ros2_control_demos存储库
目录 克隆存储库 方法 1: 使用 git clone 和 rosdep 安装依赖 方法 2: 使用 vcs 工具管理多个存储库 区别总结 rosdep 和 APT 的关系 网络问题 安装依赖 克隆存储库 方法 1: 使用 git clone 和 rosdep 安装依赖 下载存储库: mkdir -p ~/ros2_ws/src cd ~/ros…...
Apache Doris Flink Connector 24.0.0 版本正式发布
亲爱的社区伙伴们,Apache Doris Flink Connector 24.0.0 版本已于 2024 年 9 月 5 日正式发布。该版本新增了对 Flink 1.20 的支持,并支持通过 Arrow Flight SQL 高速读取 Doris 中数据。此外,整库同步所依赖的 FlinkCDC,也需升级…...
语音控制小夜灯的实现方案介绍
语音控制小夜灯的实现方案组成部分 语音控制小夜灯的实现方案主要包括硬件组装和软件编程两个部分。 硬件组装涉及将语音声控模块、灯泡、USB连接线等组件正确连接。首先,使用螺丝刀和螺丝将四个隔离柱固定在底板四个拐角处,同时将语音声控模…...
万龙觉醒免费辅助:VMOS云手机辅助巴克尔阵容搭配攻略!
《万龙觉醒》是一款策略类手游,选择合适的英雄阵容搭配能够极大提升战斗效果。而借助VMOS云手机的辅助功能,玩家可以更加轻松地管理游戏进程,优化操作体验。以下是VMOS云手机的三大核心功能,帮助你更好地掌控《万龙觉醒》战局。 V…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
