谁还没经历过死锁呢?

edcf717b67115d76145b00a9fcc42670.gif

作者 | 敖丙

来源 | 敖丙

之前刚学习多线程时,由于各种锁的操作不当,经常不经意间程序写了代码就发生了死锁,不是在灰度测试的时候被测出来,就是在代码review的时候被提前发现。

这种死锁的经历不知道大家有没有,不过怎么说都是一个面试高频题目,面试官是肯定希望你经历过的,没经历过那也得看看某八股文职业选手的文章装作经历过

那么什么是死锁呢?为什么会产生死锁呢?

什么是死锁

敖丙和小美是公司同事,今天他们参加了两个不同主题的会议。但是只有一台笔记本电脑,一个投影仪。敖丙拿了笔记本,小美拿了投影仪。

那么会议开了一半,我发现:不行啊!开会除了笔记本电脑还需要投影给别的同事看啊,而小美在另一个会议室也发现了,自己只拿个投影仪没啥用啊,这里连电脑都没有。

于是,我需要小美的投影仪,小美需要敖丙的电脑,他们都需要对方手里的资源,但是又不能放弃自己所持有的。

所以两个会议都开不下去了。

就是因为这个原因,让会议进程耽搁了两个小时。两边的老板都炸了:“ 开会前怎么连这些都没准备好,还想不想干了?!”

于是老板让敖丙写个检讨好好复盘整个事情,以及产生问题的原因。

细心的傻瓜一定发现了,为什么小美不用写呢?

当然因为小美跟老板是......亲戚呀~

0141da5c1b1990dd0d263197faddc7cc.png

上面的问题其实就是死锁,我就想着能不能用代码描述整个过程。

于是在检讨上写了以下这段代码:

public class DeadLockDemo {public static Object lock1 = new Object(); //获取笔记本电脑public static Object lock2 = new Object(); //获取投影仪public static void main(String[] args) {new Aobing().start();new Xiaomei().start();}private static class Aobing extends Thread {@Overridepublic void run() {synchronized (lock1) {System.out.println("Aobing获取到笔记本电脑");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Aobing被中断了!");}System.out.println("Aobing正在等待投影仪");synchronized (lock2) {System.out.println("Aobing获取到投影仪");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Aobing被中断了");}}System.out.println("Aobing释放投影仪");}System.out.println("Aobing释放笔记本电脑");}}private static class Xiaomei extends Thread {@Overridepublic void run() {synchronized (lock2) {System.out.println("Xiaomei获取到投影仪");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Xiaomei被中断了!");}System.out.println("Xiaomei正在等待笔记本电脑");synchronized (lock1) {System.out.println("Xiaomei获取到笔记本电脑");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Xiaomei被中断了!");}}System.out.println("Xiaomei释放笔记本电脑");}System.out.println("Xiaomei释放投影仪");}}
}

从上面程序看出来了,Aobing和Xiaomei两个线程都需要获取锁去访问各自的临界区 ,但是它们又分别依赖对方的资源。

于是两个线程就同时进入了等待对方资源释放的情况,但是谁都无法释放。

这就造成了死锁的状况。

8ffc5065be275870d4b956ee41a167f9.png

死锁排查

但是这仅仅只是一个大概率的猜测,已经知道程序出现了异常,又如何第一时间排查是不是死锁呢?我继续研究了起来。

他通过Java提供的一些检测方式,进行了快速的定位。

Jps & Jstack

Jps是Jdk自带的一个工具,可以查看到正在运行的Java进程:

9a8d8fa99b582df9fca7cec76b6a4764.png

ok,可以看到。DeadLockDemo的进程ID是1884,拿到这个进程ID,再使用jstack命令。

jstack是Java性能排查的利器,主要用来实时跟踪进程里对应线程的堆栈信息,可以将Jvm进程内的所有线程的调用栈都打印出来。

所以,直接跟踪1884这个进程ID就行。

afc270a9680c048465184c9052d83446.png

果然,可以看到,jstack已经检测到了死锁。并且Aobing和xiaomei两个线程都在互相等待对方的锁释放,也就是阻塞状态

从这里,我确认程序发生了死锁**。

马上跑过去对正在和小美喝咖啡的老说说:“ 老板,你看这真的不是我的错啊,是咱公司资源不够,发生了死锁!我写个程序都跑不出结果!”

c8d7d7c44d2ff086a326060e111707ee.png

老板道貌俨然地点了点头。“嗯,那你还是得想想怎么解决,一个问题不能连续犯两次!”

于是在当天深夜11点,敖丙进行了深刻的自我反思,默默的写下这篇文章:「一个关于死锁的故事」。

死锁的类型

OK,看完了上面的故事,我们回过头来,继续来讲关于死锁的知识。

关于死锁有几种类型呢?主要有三种:

  • 一般性死锁:这是最经典的死锁方式。指的是多线程环境下每个线程都需要多个资源去执行,但是这些资源又分别被不同的线程占有着,这就造成了一种僵持的状态。

47780f2b7fc8abf0a34c8fd7351c6522.png
  • 嵌套性死锁:指的就是锁的互相嵌套使用。我们上面故事的死锁类型,其实就属于嵌套性死锁。

884d11982f09d24cfe5f66bbfd43b8ac.png

  • 重入性死锁:指的是多线程环境下,若当前线程重复调用一个方法则可能因为代码逻辑里的边界情况从而导致死锁。

c86864500b5e23c7b276ae28430f2464.png

所以后来Java中无论是Synchronized还是Lock在可重入方面都会维护一个计数器来记录当前线程的重入次数,从而进入不同的代码逻辑,就是为了避免死锁的发生。

死锁原理

那么有的小伙伴就会担心了:“听你这么分析,我以后都不敢随意用它们了,这要是背锅了可怎么办!”。

别担心,死锁哪有那么容易发生呢。

你应该问一个问题:程序为什么会出现死锁,或者说在什么情况下,程序才会出现死锁。

要产生死锁,必须保证你的资源要能够满足以下条件,并且缺一不可

  • 互斥条件

某资源一次只能一个线程访问,该资源只要分配给某个线程,其它线程就无法再访问,直到该线程访问结束。

f2357644b1febad876f97399f6533830.png
  • 请求与保持条件

线程在已经占有至少一个资源的情况下还可以继续请求占有资源。

239fda01d91ff8afbca73af6cc1fc42d.png
  • 不可抢占条件

资源若已被其它线程占有,那么想要获取它就只能等待,不能因为你需要该资源就将其抢占。

c52c3c0a0d94b37a85879088b8576545.png
  • 循环等待条件

在竞争环境中存在一个线程等待链,使得每个线程都占有上一个线程所需的至少一种资源。

3f9d2c9405fc439309dea107baa1b6e5.png

也就是说只有以上四个条件同时满足,线程才会因为资源分配产生矛盾,死锁才有可能发生。

大家可以类比一下,敖丙和小美是不是就处于以上四个条件中呢。

所以说,不要担心,想要发生死锁还是非常不容易滴。

死锁解除

那当你确定了程序发生了死锁,怎么办呢?

当然是不要慌,先给文章点个赞,收藏一下先,确保以后能找到。

b873288193f291b89126b27578ed42ef.png

我们刚刚说了,死锁发生的情况是要同时满足互斥、请求与保持、不可剥夺、循环等待这四个条件,缺一不可。那么我们如果想要解除死锁,是不是只要将这四个条件的任意一个破坏掉就好了呢?

  • 破坏请求与保持条件

请求与保持指线程请求资源的同时必须始终持有资源,所以我们可以在线程开始运行之前,一次性地申请其在整个运行过程中所需的全部资源。直至使用完再释放。

  • 破坏不可抢占条件

想要达到这个目的代表着你要去抢占别的线程已经或正在持有的资源,这对于Synchronized是无能为力的。但是我们可以使用Lock呀!在JDK层面,juc包(java.util.concurrent)提供的Lock可以轻轻松松做到。

  • 破坏循环等待条件

若是每个线程都依赖上一线程所持有的资源,那么整个线程链就会像闭环的贪吃蛇一样,导致资源无法被释放。因此就需要某一个线程释放资源,从而打破循环。

所以,我们平时的代码要如何设计才能尽量避免死锁的发生呢?

尽量将程序设置为可中断的

将程序设置为可中断的,这样在死锁环境下如果某个线程收到中断请求之后就可以主动地释放掉手中的资源。

Java多线程中有一个重要的方法interrupt(),这个方法可以请求调用此方法的线程触发中断机制,该线程可以自身决定是否释放资源。若是已经发生了死锁,只要它放弃资源便可打破。

为锁添加时限

除此之外还可以为尝试获取锁的线程添加一个超时等待时间。若线程在规定时间内获取不到锁则放弃,这样就可以避免线程无脑请求,同时也会释放该线程已有的资源,让其它线程有机会获取到锁,可以开放化一个相对封闭的资源环境。

保持加锁顺序

对于多个线程如果需要对方所持有的锁,那么就要尽量按照相同的顺序加锁,这样就能够避免因为各个线程获取锁的顺序混乱导致死锁。

我们再回过头来看看那个关于死锁的故事。

经过昨天加班的深刻反思,我重新编写了这段代码:

public class DeadLockDemo {public static Object lock1 = new Object();  //获取笔记本电脑public static Object lock2 = new Object();  //获取投影仪public static void main(String[] args) {new Thread1().start();new Thread2().start();}private static class Thread1 extends Thread {@Overridepublic void run() {synchronized (lock1) {System.out.println("Aobing获取到笔记本电脑");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Aobing被中断了!");}System.out.println("Aobing正在等待投影仪");synchronized (lock2) {System.out.println("Aobing获取到投影仪");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Aobing被中断了");}}System.out.println("Aobing释放投影仪");}System.out.println("Aobing释放笔记本电脑");}}private static class Thread2 extends Thread {@Overridepublic void run() {synchronized (lock1) {System.out.println("Xiaomei获取到笔记本电脑");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Xiaomei被中断了!");}System.out.println("Xiaomei正在等待投影仪");synchronized (lock2){System.out.println("Xiaomei获取到了投影仪");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("Xiaomei被中断了!");}}System.out.println("Xiaomei释放投影仪");}System.out.println("Xiaomei释放笔记本电脑");}}
}

这段代码和一开始的有什么区别呢?这次它们获取锁的顺序是相同的。

Aobing和Xiaomei两个线程都是先获取lock1再获取lock2,这样子两个线程谁先获取到资源,谁就一次性持有资源,直到资源都是释放完毕再让下一个线程获取,避免相互争夺导致资源混乱,破坏了请求与保持条件。

程序也成功运行结束:

7cb8aea3655bf31eb42084291bb3371d.png

所以我决定在下次开会的时候和小美的会议时间分开。由我先一次性获取所有资源开启他的会议,结束后资源再还给小美。

我抱着电脑高兴地将这个方案告诉了老板。

第二天,由于和小美的友好配合,两个会议都愉快的开完了,会议过程非常流畅。老板很开心,决定让我担任会议编排委员,并且以后会议室不再购入新设备!

2f0ef25e7ca630365569817703fbcb25.png

我也高兴坏了,这下不仅升职加薪不再是梦,老板和小美的关系也更融洽了呢。

0dc4e41dcff9858fda6f86c034ab80f5.png

总结

其实生活中死锁的场景有很多,就像鸡生蛋蛋生鸡一样,就是一个典型的死锁Bug。都说艺术来源于生活,看来Bug也来源于生活,等量代换一下,Bug 不 就 等 于 艺 术?

2f6e2f9c1a18af311caf616a64f9828a.gif

6f05c9d1af178b324e82410972100cf5.png

往期推荐

Kubernetes 上调试 distroless 容器

「云」上玩法虽多,小心水土不服!

Redis 内存满了怎么办?这样置才正确!

Redis 为何使用近似 LRU 算法淘汰数据,而不是真实 LRU?

6e7ade0c4185327849ce80b844336870.gif

点分享

dbae55387fe5c040195d21daea13ca0f.gif

点收藏

b57372dd1305b201d46b73024e8558a2.gif

点点赞

f8c854760e9eaba7ed8c545d0b292cee.gif

点在看

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

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

相关文章

阿里巴巴超大规模Kubernetes基础设施运维体系解读

简介:ASI:Alibaba Serverless infrastructure,阿里巴巴针对云原生应用设计的统一基础设施。ASI 基于阿里云公共云容器服务 ACK之上,支撑集团应用云原生化和云产品的Serverless化的基础设施平台。 作者 | 仔仁、墨封、光南 来源 | …

搜索NLP行业模型和轻量化客户定制

简介:开放搜索NLP行业模型和轻量化客户定制方案,解决减少客户标注成本、完全无标注或少量简单标注的等问题,让搜索领域扩展更易用。 特邀嘉宾: 徐光伟(昆卡)--阿里巴巴算法专家 搜索NLP算法 搜索链路 …

CICD 的供应链安全工具 Tekton Chains

作者 | Addo Zhang来源 | 云原生指北软件供应链是指进入软件中的所有内容及其来源,简单地可以理解成软件的依赖项。依赖项是软件运行时所需的重要内容,可以是代码、二进制文件或其他组件,也可以是这些组件的来源,比如存储库或者包…

python计算不规则图形面积_python opencv中的不规则形状检测和测量

正如我在评论中提到的那样,对于这个问题,分水岭似乎是一个很好的方法.但是当你回答时,定义标记的前景和背景是困难的部分!我的想法是使用形态梯度沿着冰晶获得良好的边缘并从那里开始工作;形态梯度似乎很有效.import numpy as npimport cv2img cv2.imread(image.pn…

深度解析开源推荐算法框架EasyRec的核心概念和优势

简介:如何通过机器学习PAI实现快速构建推荐模型 作者:程孟力 - 机器学习PAI团队 随着移动app的普及,个性化推荐和广告成为很多app不可或缺的一部分。他们在改善用户体验和提升app的收益方面带来了巨大的提升。深度学习在搜广推领域的应用也…

助力公益数字化 火山引擎向公益机构捐赠多款技术产品

5月18日,字节跳动公益联合火山引擎举办了“科技应用创新让公益更美好”线上交流会,与中国红十字基金会、壹基金等多家公益机构探讨如何利用科技信息化产品提升公益事业的效率,从而进一步解决社会问题。 交流会上,火山引擎联合Pic…

云效发布策略指南|滚动、分批、灰度怎么选?

简介:在日常和用户交流过程中,我们也经常会被用户问到关于发布的问题,比如不同职能团队之间应该如何配合、发布的最佳实践应该是什么样子的等等。今天我们就来聊聊常见应用发布方式的选择,以及每种发布模式适合什么样的场景。 无论…

shell安装mysql5.7_一键部署----shell脚本安装MySQL5.7

运维开发网 https://www.qedev.com2020-11-09 12:30出处:51CTO作者:wx5ddda4c97f426一键部署----shell脚本安装MySQL5.7#/bin/bashyum-yinstallncursesbisoncmakegccgcc-cncurses-develuseraddmysql-s/sbin/nologinread-p"输入你存放压缩包的绝对路…

极致用云,数智护航

简介:我们邀请到了阿里云混合云监控平台(Sunfire)团队负责人王肇刚来给我们分析下阿里背后的数字化业务运维安全工程标准及解决方案。 本次分享涵盖了全新发布的数字化业务运维安全工程标准、安全生产解决方案,以及全新升级的产品能力:包括了…

Lakehouse 架构解析与云上实践

简介:本文整理自 DataFunCon 2021大会上,阿里云数据湖构建云产品研发陈鑫伟的分享,主要介绍了 Lakehouse 的架构解析与云上实践。 作者简介:陈鑫伟(花名熙康),阿里云开源大数据-数据湖构建云产品…

菜鸟教程 mysql like_MySQL LIKE 子句

MySQL LIKE 子句我们知道在 MySQL 中使用 SQL SELECT 命令来读取数据,同时我们可以在 SELECT 语句中使用 WHERE 子句来获取指定的记录。WHERE 子句中可以使用等号 来设定获取数据的条件,如 "runoob_author RUNOOB.COM"。但是有时候我们需要获…

云原生 Serverless Database 使用体验

简介:表格存储 Tablestore 作为一款广泛应用 Serverless DataBase,能够提供经济的计费模式,可以大幅缩减业务成本的同时, 具备极致的弹性服务能力和完全零运维的特性,能够给用户带来更丝滑的使用体验。 作者 | 李欣 …

首推业人一体,北森2022春季发布会,正式发布iTalentX5.0

5月19日,中国最大的一体化HR SaaS及人才管理平台北森于线上召开“HR x业务”2022春季新品发布会,基于“业务人力一体化”理念发布iTalentX5.0,以战略和业务为牵引,用数字化平台赋能经理、员工和HRBP, 实现人力资源与业…

【阿里云 CDP 公开课】 第二讲:CDH/HDP 何去何从

简介:Hadoop社区版CDH/HDP已经不再更新,也将终止服务。后续的平台路线图怎么规划?Cloudera CDP整合了CDH和HDP,有哪些性能提升和功能增强?如何平滑的进行迁移?本文结合CDH/HDP平台现状,详细介绍…

mysql表全连接_关于mysql 实现表连接(左,右,内,全连接)

mysql 实现表连接(左,右,内,全连接)查询中出现两个表的连接,下面通过实例来讲解一下各种连接查询的不同之处表 a,和表b 如下图a 表中 有 abcdb表中有 abcf内连接:SELECT * from a INNER JOIN b on a.nameb.…

阿里云 Serverless Kubernetes 的落地实践分享

简介:Serverless Kubernetes 基于 Kubernetes 之上,提供按需使用、节点免运维的 Serverless 能力,让开发者真正实现通过 Kubernetes 标准化 API 进行 Serverless 应用编程,值得关注。 作者 | 元毅(阿里云容器平台高级开…

DevLake 加入 Apache 孵化器,开源共建 Apache 生态首个研发大数据平台

4 月 29 日,开源研发数据平台 DevLake 通过投票决议,正式成为 Apache 软件基金会 (ASF) 的孵化项目。 进入孵化后,Apache DevLake 将遵循 The Apache Way[1],在导师们的引导下,坚持以人为本、社区高于代码的理念&#…

idea连接mysql注册登录_IDEA+MySQL实现登录注册的注册验证时出现 Cannot resolve query parameter '2'...

问题描述:在ideamysqltomcat 实现登录注册jsp的注册信息insert验证时出现 cannot resolve query parameter 2贴上创建链接的代码:if(conn ! null){string sql "select *from login_info where dbuser?";pstm conn.preparestatement(sql);ps…

如何画一张架构图(内含知识图谱)

简介:架构图是什么?为什么要画架构图?如何画好架构图?有哪些方法?本文从架构的定义说起,分享了阿里文娱高级技术专家箫逸关于画架构图多年的经验总结,并对抽象这一概念进行了深入地讨论。内容较…

软件分析与设计:分析什么?如何设计?

简介:分析与设计这两个词我们平时经常听到,也经常讲,那么分析与设计的本质究竟是什么呢?到底要分析什么?又到底要怎样去设计?这3个问题如果平时没有一些积累,突然被问到这些,一时也会…