理解JVM中的死锁:原因及解决方案

在这里插入图片描述
死锁是并发应用程序中的常见问题。在此类应用程序中,我们使用锁定机制来确保线程安全。此外,我们使用线程池和信号量来管理资源消耗。然而,在某些情况下,这些技术可能会导致死锁。

在本文中,我们将探讨死锁、死锁出现的原因以及如何分析和避免潜在的死锁情况。

理解死锁

简单地说, 当两个或多个线程在等待另一个线程持有的另一个资源可用时互相阻塞时就会发生死锁

JVM 并非为从死锁中恢复而设计的。因此,根据这些线程的操作,当发生死锁时,整个应用程序可能会停滞,或者会导致性能下降。

死锁示例

为了说明死锁现象,让我们创建一个在两个账户之间转移资金的模拟:

private static void transferFunds(Account fromAccount, Account toAccount, BigDecimal amount) {synchronized (fromAccount) {System.out.println(Thread.currentThread().getName() + " acquired lock on " + fromAccount);synchronized (toAccount) {transfer(fromAccount, toAccount, amount);}}
}public static void transfer(Account fromAccount, Account toAccount, BigDecimal amount) {if (fromAccount.getBalance().compareTo(amount) < 0)throw new RuntimeException("Insufficient funds.");else {fromAccount.withdraw(amount);toAccount.deposit(amount);System.out.println(Thread.currentThread().getName() + " transferred $" + amount + " from " + fromAccount + " to " + toAccount);}
}

乍一看,上面的代码可能没有明显地表明 transferFunds() 方法如何导致死锁。似乎所有线程都以相同的顺序获取锁。但是,锁的顺序取决于传递给 transferFunds() 方法的参数的顺序。

在我们的例子中,当两个线程同时调用transferFunds() 方法时,可能会发生死锁,一个线程将资金从account1转移到account2,另一个线程将资金从account2转移到account1

Thread thread1 = new Thread(() -> transferFunds(account1, account2, BigDecimal.valueOf(500)));
Thread thread2 = new Thread(() -> transferFunds(account2, account1, BigDecimal.valueOf(300)));thread1.start();
thread2.start();

线程1获取**帐户 1的锁并等待帐户 2的锁,而线程 2持有**帐户 2的锁并等待帐户 1的锁。

修复死锁

为了修复示例中的死锁, 我们可以定义锁的顺序,并在整个应用程序中一致地获取它们 。 这样,我们可以确保每个线程以相同的顺序获取锁。

引入对象排序的一种方法是利用它们的hashCode值。此外, 我们还可以使用System.identityHashCode ,它返回 hashCode() 方法的值

让我们修改我们的transferFunds()方法并使用**System.identityHashCode引入锁排序:

public static void transferFunds(final Account fromAccount, final Account toAccount, final BigDecimal amount) {int fromHash = System.identityHashCode(fromAccount);int toHash = System.identityHashCode(toAccount);if (fromHash < toHash) {synchronized (fromAccount) {System.out.println(Thread.currentThread().getName() + " acquired lock on " + fromAccount);synchronized (toAccount) {transfer(fromAccount, toAccount, amount);}}} else if (fromHash > toHash) {synchronized (toAccount) {System.out.println(Thread.currentThread().getName() + " acquired lock on " + toAccount);synchronized (fromAccount) {transfer(fromAccount, toAccount, amount);}}} else {synchronized (sameHashCodeLock) {synchronized (fromAccount) {System.out.println(Thread.currentThread().getName() + " acquired lock on " + fromAccount);synchronized (toAccount) {transfer(fromAccount, toAccount, amount);}}}}
}

在上面的代码示例中,我们计算了fromAccounttoAccount的哈希码,并根据给定的值定义了锁顺序。

由于两个对象可以具有相同的哈希码,我们需要添加额外的逻辑并引入第三个sameHashCodeLock锁:

private static final Object sameHashCodeLock = new Object();

在else语句中,我们首先获取了sameHashCodeLock上的锁,确保一次只有一个线程获取Account对象的锁。这消除了死锁的可能性。

避免死锁方法

进一步讨论如何避免死锁。我们应该记住,如果我们的程序一次只获取一个锁,它就永远不会遇到锁排序死锁。

指定锁定时间

我们的系统从死锁中恢复的一种方法是使用 定时锁定尝试 。我们可以使用Lock接口中的 tryLock() 方法。在该方法中,我们可以设置超时,如果方法无法获取锁,则超时后返回失败。这样,线程就不会无限期地阻塞:

while (true) {if (fromAccount.lock.tryLock(1, SECONDS)) {System.out.println(Thread.currentThread().getName() + " acquired lock on " + fromAccount);try {if (toAccount.lock.tryLock(1, SECONDS)) {try {transfer(fromAccount, toAccount, amount);} finally {toAccount.lock.unlock();}}} finally {fromAccount.lock.unlock();}}SECONDS.sleep(10);
}

我们不应该忘记在finally块中调用 unlock() 方法。

使用线程转储检测死锁

最后,让我们看看如何使用线程转储和fastThread工具检测死锁。线程转储包含每个正在运行的线程的堆栈跟踪和锁定信息。

导致死锁的生成的线程转储的一部分如下所示:

"Thread-0":waiting to lock monitor 0x000060000085c340 (object 0x000000070f994f08, a com.tier1app.deadlock.Account),which is held by "Thread-1""Thread-1":waiting to lock monitor 0x0000600000850410 (object 0x000000070f991c90, a com.tier1app.deadlock.Account),which is held by "Thread-0"

为了检查我们的应用程序是否遭遇死锁,我们可以将线程转储上传到fastThread工具中:

在这里插入图片描述

图:死锁问题突出显示快速线程工具

完整报告可在此处找到。

接下来我们来看看导致此问题的详细信息:

在这里插入图片描述

图:发现的死锁详细信息快速线程工具

写在最后

在本文中,我们了解了什么是死锁,如何修复死锁以及如何避免死锁。

总而言之,当线程在等待从另一个线程获取的资源可用时相互阻塞时,并发应用程序中就会发生死锁。修复死锁的一种方法是使用对象的哈希码定义锁定顺序。

最后,我们可以使用线程转储和fastThread工具检测死锁。

本文翻译自

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

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

相关文章

旋转机械故障诊断 震动故障分析与诊断

旋转机械故障诊断 机理资料整理 电气故障&#xff0c;机械故障(不平衡&#xff0c;不对中&#xff0c;松动&#xff0c;轴承&#xff0c;共振&#xff0c;流体振动&#xff0c;皮带松动)&#xff0c;低速与高速机器故障诊断等 旋转机械故障诊断&#xff1a;机理资料整理 目录…

河钢数字PMO牛红卫受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 河钢数字技术股份有限公司项目管理部PMO牛红卫受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“从技术到领导力——项目经理成长进阶之道”。大会将于10月26-27日在北京举办&…

数据结构——串的模式匹配算法(BF算法和KMP算法)

算法目的&#xff1a; 确定主串中所含子串&#xff08;模式串&#xff09;第一次出现的位置&#xff08;定位&#xff09; 算法应用&#xff1a; 搜索引擎、拼写检查、语言翻译、数据压缩 算法种类&#xff1a; BF算法&#xff08;Brute-Force&#xff0c;又称古典的…

NASA:ATLAS/ICESat-2 L3 B每周网格大气数据集V005

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATLAS/ICESat-2 L3B Weekly Gridded Atmosphere V005 简介 该产品报告每周全球云覆盖率、海洋上总列光学深度、极地云覆盖率、风吹雪频率、视表面反照率以及地面探测频率。 参数&#xff1a;云光学…

Java 每日一刊(第15期):内部类

文章目录 前言内部类成员内部类&#xff08;Member Inner Class&#xff09;静态内部类&#xff08;Static Nested Class&#xff09;局部内部类&#xff08;Local Inner Class&#xff09;匿名内部类&#xff08;Anonymous Inner Class&#xff09; 内部类的详细对比内部类字节…

新增用户 开发

原型分析 接口设计 数据库设计 代码开发 controller /*** 新增员工** param employeeDTO* return*/ApiOperation("新增员工")PostMappingpublic Result<String> save(RequestBody EmployeeDTO employeeDTO) {log.info("新增员工&#xff1a;{}", emp…

C++离线查询

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 概念及原理 离线算法( offline algorithms)&#xff0c;离线计算就是在计算开始前已知所有输入数据&#xff0c;输入数据不会产生变化&#xff0c;且在解决一个问题后就要立即得出结果的前提下进行的计算。 通俗的说&a…

智能优化算法-遗传算法(GA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 遗传算法 (Genetic Algorithm, GA) 是一种基于自然选择和遗传学原理的元启发式优化算法&#xff0c;它模仿了生物进化过程中的选择、交叉和变异操作来搜索最优解。 GA的工作机制主要包括&#xff1a; 选择&am…

73 矩阵置零

解题思路&#xff1a; \qquad 原地算法&#xff0c;指除原有输入资料所占空间外&#xff0c;使用额外空间尽可能少(常数空间)的算法。本题容易想到的一种解法是&#xff0c;对于m x n的矩阵&#xff0c;一次遍历把含有0元素的行号、列号记录下来&#xff0c;然后再一次遍历把对…

中序遍历二叉树全过程图解

文章目录 中序遍历图解总结拓展&#xff1a;回归与回溯 中序遍历图解 首先看下中序遍历的代码&#xff0c;其接受一个根结点root作为参数&#xff0c;判断根节点是否为nil&#xff0c;不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …

阿⾥编码规范⾥⾯Manager分层介绍-专⽤名词和POJO实体类约定

开发⼈员&#xff1a;张三、李四、王五 ⼀定要避免单点故障 ⼀个微服务起码两个⼈熟悉&#xff1a;⼀个是主程⼀个是技术leader 推荐是团队⾥⾯两个开发⼈员 N⽅库说明 ⼀⽅库: 本⼯程内部⼦项⽬模块依赖的库(jar 包)⼆⽅库: 公司内部发布到中央仓库&#xff0c;可供公司…

计算机毕业设计推荐-基于python的白酒销售数据可视化分析

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、白酒销售数据…

记一次Meilisearch轻量级搜索引擎使用

以前使用的是mysql的全文索引、最开始还行。后续觉得就不好用了&#xff0c;但是服务器资源有限&#xff0c;没法上ES&#xff0c;只好找一个轻量级的搜索引擎、找了半天&#xff0c;决定使用这一个&#xff0c;目前效果还不错的。 参考网址 官网&#xff1a;https://www.meil…

基于单片机的智能小车的开发与设计

摘要&#xff1a;本文论述了基于 STC89C52 单片机的智能小车的开发与设计过程。该设计采用单片机、电机驱动及光电循迹等技术&#xff0c;保证小车在无人管理状态下&#xff0c;能按照预先设定的线路实现自动循迹功能。在电路结构设计中力求方便&#xff0c;可操作&#xff0c;…

麦克斯韦方程组

目录 1. 高斯定律&#xff08;电场部分&#xff09; 2. 高斯定律&#xff08;磁场部分&#xff09; 3. 法拉第电磁感应定律 4. 安培定律&#xff08;带有位移电流项&#xff09; 5.麦克斯韦方程组的物理意义 麦克斯韦方程组为我们提供了一个完整的电磁场理论框架&#xff…

[Meachines] [Medium] Querier XLSM宏+MSSQL NTLM哈希窃取(xp_dirtree)+GPP凭据泄露

信息收集 IP AddressOpening Ports10.10.10.125TCP:135, 139, 445, 1433, 5985, 47001, 49664, 49665, 49666, 49667, 49668, 49669, 49670, 49671 $ nmap -p- 10.10.10.125 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrp…

CentOS 7 YUM源不可用

CentOS 7 操作系统在2024年6月30日后将停止官方维护&#xff0c;并且官方提供的YUM源将不再可用。 修改&#xff1a;nano /etc/yum.repos.d/CentOS-Base.repo # CentOS-Base.repo [base] nameCentOS-$releasever - Base baseurlhttp://mirrors.aliyun.com/centos/$rel…

【Unity Shader】Special Effects(九)Vortex 旋涡(UI)

源码:[点我获取源码] 索引 Vortex 旋涡思路分析旋涡中心旋涡旋转旋涡强度旋涡动画Vortex 旋涡 旋涡效果可以将一张图像以指定点作为旋涡中心,呈顺时针旋涡动画效果,使用动画播放器: 思路分析 首先,旋涡特效的核心也即是旋转(特别是uv坐标的旋转); 在此基础上,旋涡中…

二叉搜索树(BSTree)原理及应用场景

目录 引言 二叉搜索树的基本概念 常见算法 插入节点 查找节点 删除节点 二叉搜索树的应用场景 1. 数据库索引 2. 符号表 3. 字典和词汇表 4. 动态集合 结论 引言 二叉搜索树&#xff08;Binary Search Tree, BST&#xff09;是一种特殊的二叉树&#xff0c;其每个节…

C语言 | Leetcode C语言题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; #define MAX_LEVE_SIZE 1000 #define MAX_NODE_SIZE 10000int** levelOrder(struct Node* root, int* returnSize, int** returnColumnSizes) {int ** ans (int **)malloc(sizeof(int *) * MAX_LEVE_SIZE);*returnColumnSizes (int *)mal…