#详细介绍!!! 造成死锁的原因以及解决方案!

news2025/2/26 3:56:18

本篇主要是介绍什么是死锁,已经死锁产生的原因,如果避免死锁。根据上述的几个问题让我们来阅读本篇文章。

目录

1. 什么是死锁

2. 形成死锁的原因(四个必要条件)

3. 如果有效避免死锁




 

1. 什么是死锁

死锁主要是锁彼此间进行锁等待,导致每个锁都不能正常执行的情况

例子1:多个锁相互等待造成死锁

假设有两个锁对象为lock1,lock2

线程t1对lock1进行加锁操作,在lock1加锁操作中多lock2再进行加锁操作

线程t2先对lock2进行加锁操作,在lock2加锁操作中又对lock1进行加锁

此时就会造成死锁

伪代码:

public class Demo3 {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t1");
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while(true){
                synchronized (lock2){
                    synchronized (lock1){
                        System.out.println("hello t2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

上述代码则必然会造成死锁

t1 和  t2 线程同时执行

当t1线程对lock1加锁成功了之后,此时lock1已经被占用了,然后t2线程对lock2加锁成功了,此时lock2已经被占用了;

在这种情况下,t1线程在去尝试对lock2进行加锁则会进行锁等待,因为lock2进行被线程t2占用了,那么此时t2线程去尝试对lock1进行加锁也是同理,此时t1占用个lock1不放,t2占用着lock2不放,导致对方都拿不到锁进行等待,那么此时t1和t2线程就不能向下执行死锁了

例子2:同一个线程对同一个锁进行加锁并且该锁是不可重入锁,自己给自己进行锁等待,必然死锁

假设有一个锁lock

代码:

public class Demo4 {
    public static void main(String[] args) {
        Object lock = new Object();
        int a = 0;
        int b = 0;
        synchronized (lock){
            a++;
            synchronized (lock){
                b++;
            }
        }
    }
}

如果是不可重入锁的话上面代码必然死锁

原因,第一次对lock加锁,是此时lock已经被自己占用了,第二次再对lock加锁则发现lock已经被占用此时进行等待lock释放,但是线程已经锁等待了,此时肯定是等不到lock释放的,则造成死锁

注意:由于synchronized是可重入锁,所以上述代码并不会死锁,上面代码只是举个例子说明这种情况

2. 形成死锁的原因(四个必要条件)

本质原因就是,锁之间进行相互等待,等待不到也不会释放手中的资源,相互进行死等导致死锁

而造成这样的底层原因为如下几点:

1. 互斥使用:锁的特性是每次只能有一个线程对锁进行使用,当锁没解锁时,锁资源不会被其他线程正常获取到

2. 不可抢占:资源请求者不能从资源占用者那里抢占到资源,资源只能被占用者主动释放后,请求者才能去获取资源

3. 请求和保持:锁资源请求者在请求其他锁资源时,会保持手中锁资源的占用

如上面例1,t1占用了lock1,再去请求lock2时会仍然占用这lock1,导致其他请求者获取不到

4. 循环等待:线程之间循环占用着彼此的资源,导致彼此间都获取不到资源去正常运行导致死锁

如上面例1:t1占用这个lock1,且正在亲请求lock2,t2占用这lock2,且正在请求lock1;

此时t1占用着t2请求的资源,t2占用着t1请求的资源,这样形成了一个请求闭环,导致相互拿不到资源进而死锁

注意:只要形成了死锁,则必然满足上面四个条件

3. 如果有效避免死锁

既然需要避免死锁,那么就需要根据造成死锁的原因入手。根据上面四条必要条件

1,2点是锁为了保证线程安全锁持有的的特性改变不了

但是3,4则是编码者自己写出来的可以避免

只要破坏了3,4点其中一条自然也就不会死锁

死锁与前面的四个原因是必要关系

方法:破坏循环等待的环路

我们只要保证加锁的顺序一致,那么环路就会得到有效的破坏

前面的例1进行改写:

把两个线程的加锁顺序改为一致

public class Demo3 {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t1");
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

此时 t1 和 t2 线程都是先对lock1进行加锁,再对lock2进行加锁,此时就不会产生资源彼此占用的环路了也就不会进行死锁等待

解析:

t1先占用了lock1锁,此时t2就获取不到lock1锁了,那么此时t2就在当前代码位置进行锁等待了,更加执行不到去尝试占用lock2锁的代码了,那么t1线程就能正常拿到lock2的锁资源了,所以此时t1和t2只会执行一个线程,不会形成请求资源的闭环。


本篇文章介绍到这里就差不多了,也欢迎各位铁铁指正文章错误的地方

 

 

 

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

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

相关文章

FPGA实现JPEG-LS图像压缩,有损无损可配置,提供工程源码和技术支持

目录 1、前言2、JPEG-LS图像压缩理论3、JPEG-LS图像压缩性能介绍4、JPEG-LS图像压缩时序介绍5、JPEG-LS图像压缩输出压缩流6、工程源码和仿真7、福利:工程代码的获取 1、前言 JPEG-LS(简称JLS)是一种无损/有损的图像压缩算法,其无…

1 Go语言开发环境搭建详细教程【Go语言教程】

Go语言开发环境搭建【Win、Linux、Mac】 1 SDK下载 官网地址:golang.org,因为一些原因国内可能无法访问。可以使用下面第二个链接。国内地址访问:https://golang.google.cn/dl或者https://www.golangtc.com/download 根据自己操作系统版本,下…

30天学会《Streamlit》(9)

30天学会《Streamlit》是一项编码挑战,旨在帮助您开始构建Streamlit应用程序。特别是,您将能够: 为构建Streamlit应用程序设置编码环境 构建您的第一个Streamlit应用程序 了解用于Streamlit应用程序的所有很棒的输入/输出小部件 第9天 - …

超高精度PID控制器的特殊功能(4)——分程控制功能及其应用

摘要:分程控制作为一种典型的复杂控制方法之一,常用于聚合反应工艺、冷热循环浴、TEC半导体温度控制、动态平衡法的真空和压力控制等领域。为快速和便捷的使用分程控制,避免采用PLC时存在的控制精度差和使用门槛高等问题,本文介绍…

pyqt6+vtk

这里用PyQt6vtk9.2.6 pip install PyQt6 pyqt6-tools vtk这里拉了一个水平布局 然后水平布局中加入QWidget,object name改为vtkWidget 右键Promote Widgets Promoted class name: QVTKRenderWindowInteractor Header file: vtkmodules.qt.QVTKRenderWindowInterac…

文章改写神器在线-AI续写文章生成器

AI续写生成器 AI续写生成器是一种利用人工智能技术的创意工具,能够提高写作效率,为营销推广带来全新的可能性。无论你是写手、广告人员还是市场营销人员,这个工具都能够有效地解决你在写作中遇到的难题。 在内容创作行业中,原创…

Amazon Linux2部署安装Jenkins

先决条件 服务器配置要求 256 MB of RAM 1 GB of drive space (although 10 GB is a recommended minimum if running Jenkins as a Docker container) 需要部署安装JDK环境部署安装的Jenkins版本为Version 2.400 部署安装JDK 1. 下载JDK软件包 wget https://corretto.aws/…

Altium Designer借助嘉立创添加PCB封装和3D模型

目录 引言打开立创专业版EDA,建立项目从立创商城找到器件编码添加PCB封装导出和修改3D封装 引言 由于使用Altium Designer的频率并不是特别高,所以每一次使用总是得东跌西撞的才回忆起一些使用步骤。因此,想在这里记录一下Altium Designer借…

【mysql】binlog日志

目录 1.1 基本说明1.2 binlog日志格式1.3 binlog日志查看1.4 binlog日志删除1.5 binlog操作示例 1.1 基本说明 1.全称binary log,二进制日志 2.记录了所有的DDL语句(Data Definition Language数据定义语言)和DML语句(Data Manipul…

Java多线程中sleep()方法和wait()方法的区别

目录 具体而言 ,sleep()方法与wait()方法的区别主要表现在以下几个方面: 引申:sleep()方法和yield()方法有什么区别? 常见面试题&a…

如何衡量算法的效率?时间复杂度空间复杂度

本篇博客会讲解如何衡量一个算法的效率。衡量算法的效率,主要有2个维度,分别是:时间复杂度和空间复杂度。 时间复杂度用来衡量算法的时间效率。时间复杂度越低,算法的耗时越短,效率则越高。空间复杂度用来衡量算法的空…

Space Cloud updated Crack

Space Cloud updated Crack Space Git Flow-对于实践连续发布周期的开发团队来说,在协调团队成员、建立流程和集成多个工具时,建立交付流可能需要付出巨大努力。为了更容易做到这一点,引入了JetBrains Space Git流,这是一个完整的…

同一热卖商品的高并发写难题 - Lua脚本扣减库存方案

目录 一、Mysql高并发写时的行锁难题 二、Redis的相关命令 1. WATCH命令 2. EVAL与EVALSHA命令 三、SpringBoot执行Lua脚本代码示例 1. 依赖包 2. Lua脚本sku.lua 3. 加载Lua脚本 4. 执行Lua脚本 四、参考资料 一、Mysql高并发写时的行锁难题 通常来说,秒…

百度大模型ERNIE3.0

大模型对比 文心全景图 ERNIE 3.0

《Java8实战》第9章 重构、测试和调试

9.1 为改善可读性和灵活性重构代码 Lambda 表达式可以帮助我们用更紧凑的方式描述程序的行为。 9.1.1 改善代码的可读性 可读性非常主观,但是通俗的理解就是“别人理解这段代码的难易程度”。改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有…

【Java|基础篇】方法的定义使用、重载以及递归

文章目录 1.什么是方法2.方法的定义和使用返回值类型形参与实参方法执行过程 3.⭐方法的重载4.递归5. 总结 1.什么是方法 方法就是组织好的,可重复使用的具有某种功能的特定代码块 (类似于函数) 在我们平时写代码时,如果遇到会经常使用的一些功能相同的代码时,我们就可以把这段…

在线考试系统学员答题批改日志,实战练习

一、环境要求 sandbox-hdp 2.6.4 或同等版本自建的HadoopHiveSparkHBase 开发环境。 二、数据描述 这是一份来自于某在线考试系统的学员答题批改日志,日志中记录了日志生成时间,题目难度系数,题目所属的知识点 ID,做题的学生 ID&#xff0…

Oracle的学习心得和知识总结(二十一)|Oracle数据库可插拔数据库PDB的创建及删除

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《Oracle Database SQL Language Reference》 2、参考书籍:《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

华为OD机试(Java),5键键盘的输出

一、题目描述 有一个特殊的5键键盘,上面有a,ctrl-c,ctrl-x,ctrl-v,ctrl-a五个键。 a键在屏幕上输出一个字母a;ctrl-c将当前选择的字母复制到剪贴板;ctrl-x将当前选择的字母复制到剪贴板&#…