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

news2024/11/15 8:11:23

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

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

理解死锁

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

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.coloradmin.cn/o/2155392.html

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

相关文章

蓝桥杯模块一:LED指示灯的基本控制

模块训练一:LED指示灯的基本控制 模块1到模块13都是通过I\O模式进行设计 一、电路图 二、电路分析 1.74HC573锁存器介绍 OE端接地&#xff0c;上电即工作&#xff0c;控制LE端&#xff0c;当LE端接高电平时&#xff0c;锁存器开始工作&#xff0c;接通D和Q 2.电路工作原理分析…

C语言 | Leetcode C语言题解之第415题字符串相加

题目&#xff1a; 题解&#xff1a; char* addStrings(char* num1, char* num2) {int i strlen(num1) - 1, j strlen(num2) - 1, add 0;char* ans (char*)malloc(sizeof(char) * (fmax(i, j) 3));int len 0;while (i > 0 || j > 0 || add ! 0) {int x i > 0 ?…

SpringCloud入门(五)Nacos注册中心(上)

国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba也推出了一个名为Nacos的注册中心。Dynami Naming and Configuration Service。是阿里巴巴2018年7月开源的项目。 Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。…

nuget包管理

1、下载 下载nuget 下载nuget.exe&#xff0c;配置系统环境变量&#xff0c;打开电脑属性一高级系统设置一环境变量一系统变量&#xff0c;选择Path&#xff0c;添加nuget.exe目录 2、常用命令 nuget install System.Data.SQLITE -SolutionDirectory D:\NugetPackages\ -Packa…

基于BiGRU+Attention实现风力涡轮机发电量多变量时序预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

37. Vector3与模型位置、缩放属性

本文章给通过组对象Group (opens new window)给大家讲解一下threejs层级模型或树结构的概念。 Group层级模型(树结构)案例 下面代码创建了两个网格模型mesh1、mesh2&#xff0c;通过THREE.Group类创建一个组对象group,然后通过add方法把网格模型mesh1、mesh2作为设置为组对象g…

【Godot4.3】GraphEdit全解析(1) - 基础介绍

概述 最早系统性的讲述Godot的GraphEdit和GraphNode的教程应该是Hi小胡的了&#xff0c;也有小伙伴已经设计出一些插件或小应用用于辅助自己的项目。或者更直观的你可以去看看B站的Godot的Visual Shader教程。 我是学了好几次&#xff0c;学完就忘了用&#xff0c;本篇是基于…

Java只有国人在搞了?

从Java诞生到现在&#xff0c;在全球一直属于最大的开发平台&#xff0c;拥有着世界上最多的开发者和最活跃的社区。你说Java只有国人在搞就有点过分了&#xff0c;Java中常用的主流框架全是外国人写的&#xff0c;虽说阿里也为Java做了很多贡献&#xff0c;但你还真没有资格说…

代码随想录Day 52|题目:101.孤岛的面积、102.沉没孤岛、103.水流问题、104.建造最大岛屿

提示&#xff1a;DDU&#xff0c;供自己复习使用。欢迎大家前来讨论~ 文章目录 图论part03题目一&#xff1a;101.孤岛的总面积解题思路DFS**BFS** 题目二&#xff1a;102. 沉没孤岛解题思路 题目三&#xff1a;103. 水流问题解题思路优化 题目四&#xff1a;104.建造最大岛屿…

Windows11+Microsoft MPI v10.1.3 安装配置记录

WindowsMicrosoft MPI v10.1.3 安装配置记录 MS-MPI 安装VS中进行配置属性管理器-添加新项目属性表VC目录-包含目录链接器-常规-附加库目录链接器-输入-附加依赖项 测试 某个项目需要MPI支持&#xff0c;在此记录MS MPI的安装配置过程。 MS-MPI 安装 在微软官网下载 两个都下…

去中心化的力量:探索Web3的分布式网络

Web3作为一种新兴的网络架构&#xff0c;代表了对互联网发展的一种探索。与传统的中心化互联网模式相比&#xff0c;Web3致力于通过去中心化的方式构建更加开放和透明的数字世界。本文将探讨Web3的核心理念、技术实现及其潜在应用。 一、去中心化的核心理念 Web3的去中心化理…

深度学习02-pytorch-06-张量的形状操作

在 PyTorch 中&#xff0c;张量的形状操作是非常重要的&#xff0c;可以让你灵活地调整和处理张量的维度和数据结构。以下是一些常用的张量形状函数及其用法&#xff0c;带有详细解释和举例说明&#xff1a; 1. reshape() 功能: 改变张量的形状&#xff0c;但不改变数据的顺序…

Stable Diffusion 使用详解(12)--- 设计师风格变换

目录 背景 seg模型&#xff08;语义分割&#xff09; 描述 原理 实战-装修风格变换 现代风格 欧式风格转换 提示词及相关参数设置 模型选择 seg cn 加持 效果 还能做点啥 问题 解决方法 出图效果 二次优化调整 二次出图效果 地中海风格转换 参数修改 效果 …

软硬件项目运维方案(Doc原件完整版套用)

1 系统的服务内容 1.1 服务目标 1.2 信息资产统计服务 1.3 网络、安全系统运维服务 1.4 主机、存储系统运维服务 1.5 数据库系统运维服务 1.6 中间件运维服务 2 运维服务流程 3 服务管理制度规范 3.1 服务时间 3.2 行为规范 3.3 现场服务支持规范 3.4 问题记录规范…

C++容器list底层迭代器的实现逻辑~list相关函数模拟实现

目录 1.两个基本的结构体搭建 2.实现push_back函数 3.关于list现状的分析&#xff08;对于我们如何实现这个迭代器很重要&#xff09; 3.1和string,vector的比较 3.2对于list的分析 3.3总结 4.迭代器类的封装 5.list容器里面其他函数的实现 6.个人总结 7.代码附录 1.两…

【C++ Primer Plus习题】17.1

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> using namespace std;int main() {char …

移动登录页:让用户开启一段美好的旅程吧。

Hi,大家好&#xff0c;我是大千UI工场&#xff0c;移动登录页千千万&#xff0c;这里最好看&#xff0c;本期分享一批移动端的登录页面&#xff0c;供大家欣赏。 本次分享的是毛玻璃/3D风格的登录页。

【Unity设计模式】Unity MVC/MVP架构介绍,及MVC/MVP框架的简单应用

文章目录 什么是MVC&#xff1f;MVC眼花缭乱设计图MVP和MVC最经典的MVC的业务流程Unity MVC 框架示例1. 创建项目结构2. 实现模型3. 实现视图4. 实现控制器5. 使用示例 总结参考完结 什么是MVC&#xff1f; MVC自1982年被设计出来&#xff0c;至今都有着很大比重的使用率&…

前端项目代码开发规范及工具配置

在项目开发中&#xff0c;良好的代码编写规范是项目组成的重要元素。本文将详细介绍在项目开发中如何集成相应的代码规范插件及使用方法。 项目规范及工具 集成 EditorConfig集成 Prettier1. 安装 Prettier2. 创建 Prettier 配置文件3. 配置 .prettierrc4. 使用 Prettier 集成 …

python--基础语法(2)

1.顺序语句 默认情况下&#xff0c;Python的代码执行顺序是按照从上到下的顺序&#xff0c;依次执行的。 2.条件语句 条件语句能够表达“如果 ...否则 ...”这样的语义这构成了计算机中基础的逻辑判定条件语&#xff0c; 也叫做 分支语句。表示了接下来的逻辑可能有几种走向…