JavaEE之多线程编程:5. 死锁(详解!!!)

文章目录

    • 一、死锁是什么
    • 二、关于死锁的三种形式
    • 三、如何避免死锁

一、死锁是什么

死锁是这样的一种情形:多个同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
在这里插入图片描述

【举个例子理解死锁】
张三李四两人去吃饺子,吃饺子需要酱油和醋。
张三抄起了酱油瓶, 李四抄起了醋瓶。
张三:你先把醋瓶给我,我用完了就把酱油瓶给你。
李四:你先把酱油瓶给我,我用完了就把醋瓶给你。
如果这俩人彼此之间互不相让,就构成了死锁。
酱油和醋相当于是两把锁,这两个人就是两个线程。

二、关于死锁的三种形式

  1. 一个线程,针对同一把锁连续加锁两次,如果是不可重入锁,就死锁了。
    关于可重入锁:指的是一个线程连续针对一把锁,加锁了两次且不会出现死锁,满足这个要求就是可重入锁。
  2. 两个线程,两把锁(此时无论是不是不可重入锁,都会死锁)。
    如:有两个线程 t1、t2,有两把锁 A、B
    ① t1 获取锁 A,t2 获取锁 B;
    ② t1 尝试获取锁 B,t2 尝试获取锁 A。
    此时就会阻塞,产生死锁。
package Thread;

/**
 * @Author : tipper
 * @Description : 死锁的情况
 */
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }
            }
        });
        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("t2 加锁成功!");
                }
            }
        });
    }
t1.start();
t2.start();
}

//输出为空且一直在执行

上述代码中,很明显什么都没打印,两个线程都没有获取成功第二把锁。
在此时,死锁代码中,两个 synchronized 是嵌套关系,不是并列关系,嵌套关系说明是在占用一把锁的前提下,获取另一把锁;而并列关系则是先释放前面的锁,再获取下一把锁,这样就不会死锁了。

  1. N个线程,M把锁。(相当于第2种情况的扩充)
    此时是更容易出现死锁的情况了。
    一个经典的描述N个线程M把锁死锁的模型,哲学家就餐问题。

【哲学家就餐问题】
在这里插入图片描述

① 每个哲学家都会做两件事:思考人生,放下筷子 和 拿起左右两侧的两根筷子开始吃面条(先拿起左边,再拿起右边);
② 哲学家什么时候吃面条和思考人生都是随机的;
③ 哲学家吃面条吃多久吃完也是随机的;
④ 哲学家吃面条的过程中,会有左右相邻的哲学家如果也想吃面条,就要阻塞等待。

基于上述规则,通常情况下,整个系统可以良好运转,但是极端情况下会出现问题!
比如,同一时刻,五个哲学家都想吃面条,同时拿起左手的筷子,然后再尝试拿右手的筷子,就会发现右手的筷子都被占用了,哲学家们互不相让,这个时候就形成了死锁
在这里插入图片描述
死锁是一种严重的BUG!会导致程序的线程“卡死”,无法正常工作!

三、如何避免死锁

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

  • 互斥使用(锁的基本特性):当一个线程持有一把锁之后,另一个线程想要取得锁,就要阻塞等待;
  • 不可抢占(锁的基本特性):资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。(当锁已经被 线程1 拿到之后,线程2 只能等 线程1 主动释放,不能强行抢过来)
  • 请求和保持(代码结构):当资源请求者在请求其他的资源的同时保持对原有资源的占有。(一个线程尝试获取多把锁,先拿到 锁1 之后,再尝试获取 锁2,获取的时候,锁1 不会释放)。
  • 循环等待:即存在一个等待队列,P1占有P2的资源,P2占有P3的资源,P3占有P1的资源,等待的依赖关系,这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

其中最容易破坏的就是“循环等待”。“互斥使用”、“不可抢占”是锁本身的特性,破坏不了,对于“请求保持”来说,调整代码结构,避免编写“锁嵌套”逻辑。这个方案不一定好用,有的需求可能就是需要进行这种,获取多个锁再操作。

【破坏循环等待】
最常用的一种死锁组织技术就是锁排序。约定加锁的顺序,就可以避免循环等待。
假设有 N 个线程尝试获取 M 把锁,就可以针对 M 把锁进行编号(1,2,3…M)。
N 个线程尝试获取锁的时候,都按照固定的按编号由小到大顺序来获取锁。这样就可以避免环路等待。

比如:哲学家就餐问题,约定每个哲学家都是先拿起编号小的筷子,后拿起编号大的筷子,此时循环等待就破除了,上述系统就不会再出现死锁了。
在这里插入图片描述

【可能产生环路等待的代码】
两个线程对于加锁的顺序没有约定,就容易产生环路等待。

package Thread;
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }


        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
                synchronized (locker1) {
                    System.out.println("t2 加锁成功!");
                }
    }
}

//运行结果:
此时一直运行


【不会产生环路等待的代码】
约定号先获取 locker1,再获取 locker2,就不会环路等待。

package Thread;

/**
 * @Author : tipper
 * @Description : 死锁的情况
 */
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }
            }
        });

        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t2 加锁成功!");
                }
            }
        });
        t1.start();
        t2.start();
    }

}
//运行结果:
t1 加锁成功!
t2 加锁成功!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

24.1.25Linux shell之cal、ncal、printf

cal 命令用于在终端上显示当前月份的日历。默认情况下,它会展示当前月份的完整日历,包括星期和日期。常用的就是下面两个参数: ncal 这个比上面得的功能多,一个是会把今天的日子标注出来,一个是排版不一样&#xff1…

redis-持久化主从复制

一.主从复制 1.是什么 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主 2.能干嘛 读写分离,性能扩展(主 写 从 读) 容灾快速恢复 3 主从复制 一主二…

使用redisson控制多个springboot实例负载同时只有一个实例执行任务

一 redisson依赖 <!-- redisson 依赖--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.4</version></dependency> 二 定时任务代码 pack…

【复现】奥威亚视屏云平台文件读取漏洞_27

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 奥威亚视屏云平台拥有丰富的应用模块&#xff0c;包括结对帮扶、网络教研、教研共同体、优课汇聚、教学资源、在线巡课、AI课堂分…

Cesium介绍及3DTiles数据加载时添加光照效果对比

一、Cesium简介 Cesium原意是化学元素铯&#xff0c;铯是制造原子钟的关键元素&#xff0c;通过命名强调了Cesium产品专注于基于时空数据的实时可视化应用。熟悉GIS开发领域的读者都知道&#xff0c;Cesium是一个用于创建3D地理空间应用程序的开源JavaScript库&#xff0c;它允…

DA14531平台secondary_bootloade工程修改笔记

DA14531平台secondary_bootloade工程修改笔记 1.支持在线仿真 初始时加入syscntl_load_debugger_cfg(); 表示可以重复Jlink连接调试仿真 2.支持串口烧录&#xff0c;和支持单线线写 utilities\secondary_bootloader\includes\bootloader.h /************** 2-wire UART supp…

如何安装MeterSphere并实现无公网ip远程访问服务管理界面

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通…

Linux快速入门

目录 一. Linux的结构目录 1.1 Linux的目录结构 1.2 常用的目录介绍 二. 常用命令 # 与 $ 提示的区别 查看ip地址&#xff1a;ifconfig su&#xff1a;切换用户 cd 目录查看 查看文件内容 创建目录及文件 复制和移动 其他 tar which whereis find chmod 三. vim一般使用 四…

静态分析C语言生成函数调用关系的利器——GCC

大纲 准备工作GCC生成单文件调用关系VCG将VCG转为Dot绘制图片绘制全景图代码参考资料 在《静态分析C语言生成函数调用关系的利器——cally和egypt》中我们介绍了如何使用GCC生成RTL文件&#xff0c;然后再借助cally和egypt来分析出调用关系的方法。GCC自身有命令可以生成代码内…

YOLOv7全网独家首发:Powerful-IoU更好、更快的收敛IoU,效果秒杀CIoU、GIoU等 | 2024年最新IoU

💡💡💡本文独家改进:Powerful-IoU更好、更快的收敛IoU,是一种结合了目标尺寸自适应惩罚因子和基于锚框质量的梯度调节函数的损失函数 💡💡💡MS COCO和PASCAL VOC数据集实现涨点 收录 YOLOv7原创自研 https://blog.csdn.net/m0_63774211/category_12511937.htm…

记一次SPI机制导致的BUG定位【不支持:http://javax.xml.XMLConstants/property/accessExternalDTD】

1、前因 今天在生产环境启用了某个功能&#xff0c;结果发现有个文件上传华为云OBS失败了&#xff0c;报错如下&#xff1a; Caused by: java.lang.IllegalArgumentException: 不支持&#xff1a;http://javax.xml.XMLConstants/property/accessExternalDTDat org.apache.xal…

js中的内置对象、数学对象、日期对象、数组对象、字符串对象

js中的对象&#xff08;三种&#xff09;&#xff1a; 自定义对象 car、computer DOM对象 div、p BOM对象 window、console 内置对象 数学对象 Math &#xff08;object类型&#xff09; 1、圆周率 Math.PI 2、向下取整(返回值) Math.floor() 3、向上取整(返回值) M…

实现自己的mini-react

实现自己的mini-react 创建运行环境实现最简单mini-react渲染dom封装创建虚拟dom节点封装函数封装render函数对齐react 调用方式使用 jsx 任务调度器&fiber架构封装一个workLoop方法 统一提交&实现 function component统一提交实现支持 function component 进军 vdom 的…

6轴机器人运动正解-逆解控制【1】——三种控制位姿的方式

概览&#xff1a; 通过运动学正解控制机器人运动通过运动学逆解控制机器人运动一个简单的物体搬运&#xff08;沿轨迹运动&#xff09; 后续会陆续更新&#xff08;本例仅供学习交流用&#xff09; 一、6轴机器人 二、运动正解控制 通过修改各个轴的角度&#xff0c;实现末…

目标检测数据集 - 人脑肿瘤检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;人脑肿瘤检测数据集&#xff0c;真实 CT 场景高质量图片数据&#xff0c;涉及人脑 CT 图片数据集丰富&#xff1b;适用实际项目应用&#xff1a;CT 图片场景下人脑肿瘤检测项目&#xff0c;以及作为通用人脑检测数据集场景数据的补充&#xff1b;标注说明…

Linux 一键部署influxd2-telegraf 二进制方式

influxd2前言 influxd2 是 InfluxDB 2.x 版本的后台进程,是一个开源的时序数据库平台,用于存储、查询和可视化时间序列数据。它提供了一个强大的查询语言和 API,可以快速而轻松地处理大量的高性能时序数据。 telegraf 是一个开源的代理程序,它可以收集、处理和传输各种不…

数据分析 - python 数据处理

数据处理 去除重复数据 # 删除重复值 保留重复行 第一行的数据 data.drop_duplicates(inplaceTrue, keepfirst)数据格式转化 日期格式化 data[order_date] pd.to_datetime(data[order_dt], format%Y%m%d)data[销售时间] pd.to_datetime(data[销售时间]) # 交货时间 销售…

JS进阶-深入面向对象(三)

看文章可以得到的收获&#xff1a; 1.在日常开发中&#xff0c;我们在声明一个数组对象后&#xff0c;没有声明有map&#xff0c;filter等方法&#xff0c;为什么可以调用这些方法呢&#xff1f; 2. 什么是面向过程思想&#xff0c;什么是面向对象思想呢&#xff1f; 3.JS中…

算法基础之线段树

文章目录 线段树 线段树 线段树的原理十分简单&#xff0c;但是在代码上会相对复杂一点 他也是用来维护一个序列&#xff0c;是一个完全二叉树的形状 对于每一个节点是一个结构体 struct Node {int L,R; int sum; // 以和为例 };假设序列为1到7&#xff0c;那么根节点存的…

EasyCVR视频融合平台雪亮工程视频智能监控方案设计与应用

随着科技的不断发展&#xff0c;视频监控已经成为城市安全防范的重要手段之一。为了提高城市安全防范水平&#xff0c;各地纷纷开展“雪亮工程”&#xff0c;即利用视频智能监控技术&#xff0c;实现对城市各个角落的全方位、全天候监控。本文将介绍一种雪亮工程视频智能监控方…