死锁(Dead Lock)

目录

一. 死锁出现的场景

1. 一个线程, 一个锁对象

2. 两个线程, 两个锁对象

3. N个线程, M个锁对象

二. 造成死锁的必要条件

1. 锁是互斥的

2. 锁是不可被抢占的

3.请求和保持

4. 循环等待

三. 死锁的解决方案

1. 预防死锁

2. 死锁产生后的解决


一. 死锁出现的场景

1. 一个线程, 一个锁对象

当程序中只有一个线程和一个锁对象的时候, 如果这个线程针对这把锁连续加锁两次, 那么就会出现"死锁"的情况.

 void func() {synchronized(this) { //第一层锁synchronized(this) { //第二层锁System.out.println("死锁");}}}

如上述代码, 当某一个对象A调用这个方法的时候: 外层的synchronized直接拿到对象A, 顺利执行里面的语句, 执行到内层的synchronized时候, 此时对象A已经被占用, 那么此时内层的synchronized就会阻塞等待, 等待外层的synchronized释放对象A, 而外层的synchronized要释放对象A, 就必须把内层的synchronized执行完. 但是, 内层的synchronized执行完又要外层的synchronized把锁资源释放. 这样就形成了一个死循环, 当代码走到内层的synchronized的时候就会一直阻塞等待. 这就是"死锁"问题.

但是, 这样的逻辑放到java代码中并不会出现死锁问题, 这是因为java底层针对这种情况设计了"可重入锁", "可重入锁"针对两次加锁和多次加锁的情况做了特殊处理. 当一个线程第一次加锁的时候, 会加锁成功, 当后续再再有线程对相同的锁对象加锁时, 系统就会判断当前进行加锁的线程是否是当前锁持有的线程(前一次对这个锁对象加锁的线程), 如果是, 那么这个锁不会进行任何的阻塞操作, 而是"放行", 继续执行里面的代码. 示意图如下:

类似地, 如果嵌套多层: 每次要进行加锁的线程,要获取的锁对象都和前面一样的话,  那么真正加锁的只有最外层, 真正释放锁资源的也只有最外层. 示意图如下:

然而, 在C++ / Python中, 并没有像java这样的机制, 所以1遇到这样的情况就真的会出现"死锁问题". 程序会一直处于"阻塞等待"状态. 

2. 两个线程, 两个锁对象

当有两个线程, 两把锁子时:

第一步: 线程1对锁对象A加锁, 线程2对锁对象B加锁.

第二步: 线程1在不释放A的情况下, 再对B加锁;  线程2在不释放B的情况下, 再对A加锁.

这样操作也会造成"死锁"问题.

具体演示代码如下:

public class Demo17 {private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("t1 加锁 locker1 完成");// 这里的 sleep 是为了确保, t1 和 t2 都先分别拿到 locker1 和 locker2 然后在分别拿对方的锁.// 如果没有 sleep 执行顺序就不可控, 可能出现某个线程一口气拿到两把锁, 另一个线程还没执行呢, 无法构造出死锁.try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t1 加锁 locker2 完成");}}});Thread t2 = new Thread(() -> {synchronized (locker1) {System.out.println("t2 加锁 locker1 完成");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t2 加锁 locker2 完成");}}});t1.start();t2.start();}
}

 t1线程拿到locker1后, 想拿locker2, 但是locker2正在被t2线程占有, 需要等待t2线程释放locker2. 而t2线程要能释放locker2, 就必须执行完语句, 必须拿到locker1. 而locker1要想释放t1线程就必须执行完语句, 就必须拿到locker2. 所以现在程序又陷入了一个"死循环"状态. 程序会一直处于"阻塞等待"状态, 形成"死锁".

通过运行结果, 我们看到, t1加锁locker1, t2加锁locker2之后, 程序就形成了"死锁", 进入了"阻塞等待"状态, 不会再往下执行了. 

3. N个线程, M个锁对象

跟上面的第二种情况一样, 这种情景下每个线程需要拿到两个锁, 才能正常的执行完代码. 其他情况都会出现"死锁".

那么我们如何能保证每个线程都能顺利拿到两个锁对象呢? --> 这里就涉及到了操作系统中一个非常经典的问题: 哲学家就餐问题.

现在有五个哲学家围着一个圆桌, 圆桌中心有一份面, 没两个哲学家中间只有一根筷子. 那么我们如何保证每个哲学家都能顺利地吃到面呢? 

我们可以约定, 让哲学家每次拿筷子的时候, 只能拿编号较小的筷子, 那么1, 2号哲学家都会尝试拿1号筷子, 3号哲学家尝试拿2号筷子, 4号哲学家尝试拿3号筷子, 5号哲学家尝试拿4号筷子. 那么这样一来, 5号筷子就空闲了. 所以5号哲学家就会同时拿起4号和5号筷子, 执行完他的任务, 然后放下4号和5号筷子. 此时4号哲学家就会拿起3号和4号筷子, 执行完任务之后放下筷子. 然后3号哲学家拿起2号和3号筷子 ...... 最终1号哲学家拿起1号和5号筷子执行完它的任务, 所有线程顺利执行完毕.

二. 造成死锁的必要条件

1. 锁是互斥的

锁的互斥性是锁的基本特性. 指一个锁资源同一时间只能被一个线程拿到. 一旦一个线程占用了这个锁资源, 其他线程必须等待, 直到这个锁资源资源被释放.

2. 锁是不可被抢占的

锁的不可抢占性也是锁的基本特性. 当某一线程占用了一个锁资源时, 另一线程不能强行抢占这个锁资源. 必须等待这个锁资源被释放.

3.请求和保持

线程t1, 在拿到锁A并且没有释放锁A的前提下, 去拿锁B, 就会产生死锁. (如果线程t1先释放了A, 再去拿B, 是没有任何问题的)

4. 循环等待

线程t1等待线程t2占有的资源, 线程t2等待线程t1占有的资源. 这样就产生了相互等待, 循环依赖的状况. 循环等待的状况一旦产生, 就会一直持续下去, 造成"死锁".

三. 死锁的解决方案

1. 预防死锁

(二)中的四个条件是形成死锁的必要条件, 缺一不可. 不满足其中一个条件就不能形成死锁. 我们也可以根据这个, 破坏四个条件中的任意一个, 就能避免死锁问题的发生.

例如: 我们可以将资源有序分配, 避免因锁资源竞争而产生的等待.  -->  线程必须按照特定的某种顺序请求资源 (我们可以对所有资源进行编号, 令线程只能按照编号的顺序请求资源).

2. 死锁产生后的解决

导致死锁产生之后, 只能通过人工干预来解决. 比如重启服务, 或者kill掉产生死锁的线程.

 好了, 本篇文章就介绍到这里啦, 大家如果有疑问欢迎评论, 如果喜欢小编的文章, 记得点赞收藏~~

 

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

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

相关文章

【Android 系统中使用CallStack类来追踪获取和操作调用栈信息】

Android系统CallStack类的使用 定义使用方法使用场景注意事项应用举例 定义 在 Android 系统中,CallStack 类是一个用于获取和操作调用栈信息的工具类。这个类通常用于调试和日志记录,以帮助开发者了解函数调用的顺序和位置。以下是您提供的代码片段的解…

深度学习基础知识-残差网络ResNet

目录 一、ResNet 的核心思想:残差学习(Residual Learning) 二、ResNet 的基本原理 三、ResNet 网络结构 1. 残差块(Residual Block) ResNet 的跳跃连接类型 2. 网络结构图示 四、ResNet 的特点和优势 五、ResNe…

【Mac】安装 VMware Fusion Pro

VMware Fusion Pro 软件已经正式免费提供给个人用户使用! 1、下载 【官网】 下拉找到 VMware Fusion Pro Download 登陆账号 如果没有账号,点击右上角 LOGIN ,选择 REGISTER 注册信息除了邮箱外可随意填写 登陆时,Username为…

基于springboot+vue实现的网上书店系统 (源码+L文)

基于springbootvue实现的网上书店系统 (源码L文)4-104 5.1 系统主要功能设计 整体系统的主要功能模块如图5-1: 图5-1系统总体功能图 5.1.1 用户端功能 用户端的主要功能设计包括首页、图书信息、商城公告、购物车等模块,这些功…

鸿蒙5.0时代:原生鸿蒙应用市场引领开发者服务新篇章

前言 10月22日原生鸿蒙之夜发布会宣布HarmonyOS NEXT正式发布,首个版本号:鸿蒙5.0。这次“纯血鸿蒙”脱离了底层安卓架构成为纯国产的独立系统,仅凭这一点就有很多想象空间。 目前鸿蒙生态设备已超10亿,原生鸿蒙操作系统在中国市…

Python 多个版本管理 -- 最简方式

目录 一、下载Python文件 二、安装文件,并配置环境变量 三、重命名Python.exe 四、配置完毕,开始使用,效果图 一、下载Python文件 Python 官方地址The official home of the Python Programming Languagehttps://www.python.org/downloa…

C#的Event事件示例小白级剖析

1、委托Delegate 首先说一下delegate委托,委托是将方法作为参数进行传递。 // 定义了一个委托类型public delegate void MyDelegate(int num);// 定义了一个啥也不干的委托实例public MyDelegate m_delegate _ > {};// 定义了一个和委托相同格式的方法public …

流畅!HTMLCSS打造网格方块加载动画

效果演示 这个动画的效果是五个方块在网格中上下移动&#xff0c;模拟了一个连续的加载过程。每个方块的动画都是独立的&#xff0c;但是它们的时间间隔和路径被设计为相互协调&#xff0c;以创建出流畅的动画效果。 HTML <div class"loadingspinner"><…

Java Iterator 实现杨辉三角

一、问题描述 杨辉三角定义如下&#xff1a; 1/ \1 1/ \ / \1 2 1/ \ / \ / \1 3 3 1/ \ / \ / \ / \1 4 6 4 1/ \ / \ / \ / \ / \ 1 5 10 10 5 1 把每一行看做一个list&#xff0c;试写一个 Iterator&#xff0c;不断输出下一行的 list&#xf…

PostGis--几何构造函数

目录 1、简介2、ST_Centroid / ST_PointOnSurface3、ST_Buffer4、ST_Intersection5、ST_Union6、substr和substringPS: 1、简介 接着上一个文章&#xff1a; 到目前为止&#xff0c;我们看到的所有函数都“按原样”处理几何图形并返回 对象分析&#xff08;ST_Length&#xf…

衡石分析平台最佳实践-开发场景之分层级嵌入

分层级嵌入 平台整体嵌入 在这种应用场景中&#xff0c;把所有功能通过 iframe 的方式都开放给登陆用户&#xff0c;嵌入的示例如下&#xff1a; html <iframename""src"https://preview.hengshi.com/app/1"> </iframe> 1 2 3 4 单个模…

数字信号处理Python示例(5)使用实指数函数仿真PN结二极管的正向特性

文章目录 前言一、二极管的电流-电压关系——Shockley方程二、PN结二极管正向特性的Python仿真三、仿真结果分析写在后面的话 前言 使用Python代码仿真了描述二极管的电流-电压关系的Shockley方程&#xff0c;对仿真结果进行了分析&#xff0c;说明在正向偏置区域&#xff0c;…

科普之使用Lableme图像标注—盲道分割与目标检测

使用Lableme图像标注—盲道分割与目标检测 数据集格式 在介绍使用Lableme软件进行数据集的标注之前&#xff0c;首先先对计算机视觉领域最知名的两个数据集的格式来进行简单的复习或者说是重新的学习。 在读研之后自己最常用的几个数据集进行存在在磁盘中跑代码的时候在拿出来…

接口测试(十)jmeter——关联(正则表达式提取器)

一、正则表达式 常用的元字符 元字符&#xff1a;用来匹配相关字符 万能匹配表达式&#xff1a; .*? 所有log结尾的文件&#xff1a;*.log 代码说明.匹配除换行符以外的任意字符\w匹配字母或数字或下划线或汉字\s匹配任意的空白符\d匹配数字\b匹配单词的开始或结束^匹配字符…

2016年7月和8月NASA的气候成像(ATom)-1飞行活动期间测量的黑碳(BC)质量混合比(单位为ng BC / kg空气)

目录 简介 摘要 代码 引用 网址推荐 知识星球 机器学习 简介 ATom: Black Carbon Mass Mixing Ratios from ATom-1 Flights 该数据集提供了在2016年7月和8月NASA的气候成像&#xff08;ATom&#xff09;-1飞行活动期间测量的黑碳&#xff08;BC&#xff09;质量混合比&…

关于Linux系统调试和性能优化技巧有哪些?

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于Linux系统调试和性能优化技巧的相关内容…

scala Map集合

一.Map的概述 Map是一种存储键值对的数据结构&#xff0c;Map中的键都是唯一的。 idea实例 二.Map的常见操作 idea实例 三.Map中的查询元素 idea实例 四.Map的常用方法 idea实例 五.Map的遍历 idea实例

Ubuntu学习笔记 - Day2

文章目录 学习目标&#xff1a;学习内容&#xff1a;学习笔记&#xff1a;Linux系统启动过程内核引导运行init运行级别系统初始化建立终端用户登录系统 Ubuntu关机关机流程相关命令 Linux系统目录结构查看目录目录结构 文件基本属性读写权限命令 下载文件的方法安装wget工具下载…

Rust 力扣 - 2841. 几乎唯一子数组的最大和

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历长度为k的窗口&#xff0c;用一个哈希表记录窗口内的所有元素&#xff08;用来对窗口内元素去重&#xff09;&#xff0c;我们取哈希表中元素数量大于等于m的窗口总和的最大值 题解代码 use std::coll…

从 vue 源码看问题 — vue 如何进行异步更新?

前言 在上一篇 如何理解 vue 响应式&#xff1f; 中&#xff0c;了解到响应式其实是通过 Observer 类中调用 defineReactive() 即 Object.defineProperty() 方法为每个目标对象的 key&#xff08;key 对应的 value 为非数组的&#xff09; 设置 getter 和 setter 实现拦截&…