【线程安全】死锁问题及解决方案

news2024/10/6 22:20:30

1. 什么是死锁

比如上一次讲到 synchronized 的时候,一个线程,对同一个对象连续加锁两次,如果出现阻塞等待,代表这个锁是不可重入锁,这样的线程,也就称为死锁!

一旦程序进入死锁了就会导致线程僵住了,无法继续执行后续的工作了,程序也就出现了严重的 BUG!

而死锁这样的情况是很隐蔽的,在开发阶段,不经意间就可能出现死锁的状态!

2. 死锁的三个典型情况

2.1 一个线程一把锁

一个线程,对同一个对象,重复加锁两次,如果不支持可重入,即会出现死锁现象,但是在 Java 中,synchronized 和 ReentarntLock(后面讲) 都是支持可重入的!

所以在 Java 中,也就不会出现这样的情况,但不排除在其他语言会出现这种情况。

2.2 两个线程两把锁

举一个生活中的例子来让大家更好的理解:

有一天张三和小美是俩社恐人员,他俩去吃牛排,由于小美吃不了太多,于是他俩就点了一份牛排,服务员只给了一个刀和叉,此时张三一把夺过刀,小美一把夺过叉,此时小美对张三说,你先把刀给我,我吃一口就给你,张三说不行,你先把叉子给我,我吃一口再给你,于是这样她俩谁都不让着谁,导致谁都吃不上牛排了!

如果把上述例子放在编程中,就比如张三和小美是两个线程,张三获取到对象A的锁,小美获取到对象 B 的锁,但是他俩还尝试获取对方的锁!

用代码表示如下:

public static void main(String[] args) {
    Object knife = new Object(); //刀
    Object fork = new Object(); //叉
    Thread t1 = new Thread(() -> {
        synchronized (knife) {
            System.out.println("张三拿到刀了!");
            synchronized (fork) {
                System.out.println("张三刀和叉都拿到了!吃牛排!");
            }
        }
    });
    Thread t2 = new Thread(() -> {
        synchronized (fork) {
            System.out.println("小美拿到叉了!");
            synchronized (knife) {
                System.out.println("小美刀和叉都拿到了!吃牛排!");
            }
        }
    });
    t1.start();
    t2.start();
}

打印结果:

此时就发现,代码僵住了,其实也就是 t1 在等 t2 释放锁,而 t2 也在等 t1 释放锁,此时两个线程都无休止的阻塞等待下去了,最终导致,张三拿不到刀叉,小美也拿不到刀叉!都不吃成牛排了!

上述代码的写法是有小概率让张三同时拿到刀和叉的,这个取决于 CPU 的调度。

这里可以通过 jconsole 工具来查看一下线程的情况:

这里发现线程进入了 BLOCKED 状态,前面讲解线程状态的时候说过,BLOCKED 状态是等待锁时产生的状态,当然也可以给上述代码添加获取线程状态的方法,也能发现是 BLOCKED 状态:

Thread.sleep(1000); // 保证进入阻塞状态
System.out.println(t1.getState()); // 查看t1线程状态
System.out.println(t2.getState()); // 查看t2线程状态

2.3 多个线程多把锁

多个线程多把的情况跟上述 2.2 的情况差不多,相当于是 2.2 的一般情况!

在很多资料上有一个典型的案例 "哲学家就餐问题" !

有一个桌子,围着一圈哲学家,每个哲学家面前放着一碗面,哲学家两两之间放一只筷子,而桌子上的哲学家只会做两件事:吃面(获取到锁,执行后续代码)或者思考人生(阻塞等待)。

当哲学家吃面的时候,就会拿起左右手的筷子(先拿左边,再拿右边),当哲学家思考人生的时候,就会放下左右手的筷子。

如果哲学家拿到两根筷子了,就会吃面,没拿到就会思考人生!

极端情况来了!如果五个哲学家同时都拿起左手边的筷子,接着每个人都去拿自己右手边的筷子,发现右边的筷子都被别人拿走了!都要等右边的哲学家把筷子放下,此时就僵住了!由于哲学家们互不相让,此时也就形成了死锁的现象。


3. 如何避免死锁

3.1 产生死锁的四个必要条件

互斥使用:当 t1 线程拿到了锁,t2 如果也想拿,必须等着,等 t1 释放了(锁的基本特性)

不可抢占:t1 拿到了锁,必须由 t1 主动释放,t2 不能强行获取锁!

保持和请求:t1 拿到了锁 A,再拿到了 B 的锁,此时 A 这把锁还是保持的(不会因为获取到了锁 B 就把 锁 A 给释放了)

循环等待:当 t1 尝试获取锁A和B,t2也尝试获取锁B和A,如果 t1 获取到锁A,t2 获取到锁B,此时 t1 就会等待 t2 释放锁B,而 t2 也会等待 t1 释放锁 A

所以只要打破这四条的任意一条,就能让死锁消失,但前三条对于 synchronized 来说,都是基本的特性,修改不了,而循环等待上述唯一一个和代码结构相关的,也是咱们可以控制的!

所以解决死锁最简单可靠的办法,就是打破循环等待!

3.2 打破循环等待

如何打破循环等待呢?就比如上述张三和小美吃牛排的问题!

张三一把夺过刀,小美一把夺过叉,于是张三想了个公平的办法,对小美说:"这样的情况,我们都吃不到牛排,我们做个约定吧,把刀叉一起放着,数到一就一起抢,但是有一个抢的顺序,只能先刀在抢叉"。

这就好比对刀和叉进行了编号,约定好,想要拿刀叉,可以,但是必须先拿刀,后拿叉!

于是张三数到一,一瞬间张三抢到了刀,此时小美没有抢到刀,由于前面的约定,小美只能等张三放下刀了,于是张三就顺利吃到了牛排!

代码实现:

public static void main(String[] args) throws InterruptedException {
    Object knife = new Object(); //刀
    Object fork = new Object(); //叉
    Thread t1 = new Thread(() -> {
        // 先对刀加锁, 才能对叉加锁
        synchronized (knife) {
            System.out.println("张三拿到刀了!");
            synchronized (fork) {
                System.out.println("张三刀和叉都拿到了!吃牛排!");
            }
        }
    });
    Thread t2 = new Thread(() -> {
        // 先对刀加锁, 才能对叉加锁
        synchronized (knife) {
            System.out.println("小美拿到刀了!");
            synchronized (fork) {
                System.out.println("小美刀和叉都拿到了!吃牛排!");
            }
        }
    });
    t1.start();
    t2.start();
}

打印结果:

做了约定之后(对锁编号),此时死锁的问题就迎刃而解了!

那么对于哲学家吃面的问题,也是如此,对每根筷子进行编号,约定好,只能先拿左右手编号小的筷子,再拿编号大的筷子。

此时这样一来,总有一个人拿不到筷子,那么上述情况,拿到编号为 4 筷子的哲学家就能拿编号为 5 筷子吃面了,吃完了放下两支筷子,接着右手边拿到 3 筷子的哲学家就拿起放下的 编号4 筷子进行吃面了,以此类推.....

本质上我们这里讲的避免死锁的方案,就是约定加锁顺序!约定顺序后,死锁的问题就解决了!也就打破了第四点的循环等待!

其实还有一种算法,银行家算法, 但实际开发中不推荐使用,比起上述讲的办法银行家算法更复杂,也更容易出错,所以更推荐上述约定顺序的方法!

如果对银行家算法感兴趣的,可以自行查阅下相关资料!


下期预告:【多线程】volatile 关键字

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

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

相关文章

低代码开发重要工具:JVS列表页字段样式配置说明

列表页中,通常存在各种各样的样式控制,例如字段宽度需要可调、字段的颜色根据内容变化等,那么我们接下来介绍下字段的样式控制的内容以及对应的效果。 1、字段样式控制配置位置 进入列表页的 数据配置界面,每个字段可以有独立的配…

在外远程控制我的世界服务器 - MCSM面板【端口映射】

文章目录 概述1.MCSManager 安装2.内网穿透2.1 安装cpolar内网穿透 3. 访问公网地址4.固定公网地址4.1 保留一个二级子域名4.2 配置固定二级域名4.3 访问固定公网地址 5. 设置节点公网地址6. 固定节点公网地址6.1 保留一个固定tcp地址6.2 配置固定TCP地址 转载自远程穿透文章&a…

从零开始学习Linux运维,成为IT领域翘楚(七)

文章目录 🔥Linux下常用软件安装_JDK和Tomcat安装🔥Linux下常用软件安装_MySQL安装🔥Linux下常用软件安装_MySQL卸载 🔥Linux下常用软件安装_JDK和Tomcat安装 Jdk 安装 解压jdk安装包 tar -zxvf jdk-8u201-linux-x64.tar.gz -C/…

中国核心生态区类型及土地利用数据有哪些,如何进行获取

全国生态功能区划是在全国生态调查的基础上,分析区域生态特征、生态系统服务功能与生态敏感性空间分异规律,确定不同地域单元的主导生态功能,制定全国生态功能区划,对贯彻落实科学发展观,牢固树立生态文明观念&#xf…

顺序存储二叉树线索化二叉树

顺序存储二叉树&线索化二叉树 文章目录 顺序存储二叉树&线索化二叉树顺序存储二叉树介绍代码实现 线索化二叉树介绍代码实现 顺序存储二叉树 介绍 背景:从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树…

我把大厂起诉了,协商拿了2N,整理点经验心得给大家,关于离职时如何让自己利益最大化!...

离职时,如何让自己的利益最大化? 一位跟大厂仲裁,并通过协商拿到2n的网友分享了自己的经验心得,干货满满,下面是要点总结: 1.大部分裁员都是违法裁员,应该给2n,但公司不会承认&#…

排序算法 — 归并排序

文章目录 归并排序介绍从下往上的归并排序从上往下的归并排序 归并排序实现从上往下的归并排序从下往上的归并排序 归并排序的时间复杂度和稳定性归并排序时间复杂度归并排序稳定性 代码实现核心&总结 每日一道算法,提高脑力。第五天(时隔7天,终于回…

mac下用git客户端生成ssh秘钥并配置到souretree进行使用

一、使用git 生成 ssh 密钥 1、Mac 安装 git 客户端 打开终端,执行命令: $ brew install git2、执行命令 $ git config --global user.name "xxx" 你自己的名字 $ git config --global user.email "xxxxxx.com&q…

Educational-Codeforces-Round-147-Rated-for-Div-2

title: Educational Codeforces Round 147 (Rated for Div. 2) date: 2023-04-21 15:47:29 categories: AlgorithmCodeforces tags:codeforcesdiv2 Educational Codeforces Round 147 (Rated for Div. 2) A. Matching 题目大意 给你一个字符串,里面包含数字和?,…

Redis缓存穿透和雪崩

Redis缓存穿透和雪崩 Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题…

AI 工具合辑盘点(七)持续更新 之 AI 音频生成工具

AI 音频生成工具 想要不亲自录制,快速将文本转换为语音?AI 音频生成工具为你提供数千种语音选择,从“普通人”的声音到模仿演员、政治家或电影角色的合成声音,各种声音应有尽有 🗣 AI 音频生成工具可用于创建商业用途…

模糊PID模糊控制(清晰化方法梯形图实现)

模糊PID的模糊化请参看下面的博客文章: 博途PLC模糊PID三角隶属度函数指令(含Matlab仿真)_plc 模糊pid_RXXW_Dor的博客-CSDN博客三角隶属度函数FC,我们采用兼容C99标准的函数返回值写法,在FB里调用会更加直观,下面给大家具体讲解代码。常规写法的隶属度函数FC可以参看下…

Python小姿势 - Python中的列表推导式

Python中的列表推导式 Python中的列表推导式是一种很好的创建列表的方式。它允许你将一个操作应用于列表中的每个元素,并将结果放入一个新的列表中。 例如,假设你有一个包含数字的列表,但是你想将每个数字都乘以2,并将结果放入一个…

第三十二章 Unity Mecanim动画系统(上)

在上一章节中,我们介绍了Unity的旧版动画系统,本章节来介绍新版的Mecanim动画系统。新版的Mecanim动画系统实际是对旧版动画系统的升级。新版的Mecanim动画系统仍然是建立在动画片段的基础上的,只不过它给我们提供了一个可视化的窗口来编辑动…

服务攻防-数据库安全-服务应用的安全问题以及测试流程-MysqlHadoop未授权访问RCE-漏洞复现

目录 一、服务应用的安全问题 1、配置不当——未授权访问 2、安全机制——特定安全漏洞 3、安全机制——弱口令爆破攻击 二、服务应用的安全测试思路 1、判断服务是否开放 2、判断服务类型 3、判断利用方式 三、Mysql-未授权访问-CVE-2012-2122 利用 1、漏洞概述 2、…

Detours HOOK

参考文本 如何使用Detours库进行DLL注入,拦截API - 知乎 (zhihu.com) 解决‘nmake‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。_nmake 不是内部或外部命令,也不是可运行的程序 或批处理文件。_AI浩的博客-CSDN博客 Detours使用方法,简单…

五音不全?手把手教你用自己声音唱任何歌;最详细的Auto-GPT整理;4月AI绘画模型推荐;HayoAI平台简直太酷了 | ShowMeAI日报

👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 🤖 『ChatGPT Code Interpreter Magic』魔法!离谱!正在怀疑人生… OpenAI 近期面向部分用户发放了 Code Interp…

一起单测引起的项目加载失败惨案 | 京东云技术团队

作者:京东科技 宋慧超 一、前言 最近在开发一个功能模块时,在功能自测阶段,通过使用单测测试功能的完整性,在测试单测联通性使用到静态方法测试时,发现单测报错,通过查阅解决方案发现需要对Javaassist包进…

vue3+webpack4 前端优化首屏时间

项目背景 中小项目,Vue-cli3 vue2 webpack4 目标 缩短白屏时间,用户能够更快的看到我的页面! 白屏时间:从打开页面到看到页面,中间白屏停留的时间。 方向 1.减少资源体积,从而缩短请求时间 2.减少资…

企业数字化转型为什么难?低代码平台能为企业带来什么?

企业数字化转型困难原因是多方便的,比如: 遗留系统:许多企业在难以替换或与新技术集成的遗留技术系统上投入了大量资金。 变革阻力:企业越大,参与决策的人就越多,让每个人都接受新工作方式的难度就越大。 …