多线程——“死锁”

news2024/9/24 21:34:48

目录

·前言

一、一个线程,一把锁

1.问题介绍

2.可重入锁 

二、两个线程,两把锁

1.问题介绍

2.解决方式

三、N个线程,M把锁

1.哲学家就餐问题

2.解决方式

·结尾


·前言

        “死锁”是多线程代码中一类常见的问题,加锁是能解决线程安全的问题,但是如果加锁的方式不当,就可能产生“死锁”,本篇文章就会对“死锁”的三个比较常见的场景进行介绍。

一、一个线程,一把锁

1.问题介绍

        当你在编程过程中,使用的锁是不可重入锁,并且在同一个线程中对同一把锁进行两次加锁,此时就可能面临“死锁”的状况,代码如下,只不过在Java语言中,锁都是可重入锁,所以并不能演示出“死锁”的效果,不过相关的逻辑可以用Java代码进行表示,如下图所示:

        上面这段代码,如果锁是不可重入锁就会出现“死锁”,因为第二次对locker进行加锁时由于locker还没有被第一次加锁操作释放所以会进入阻塞等待,然而想要释放locker必须执行完第一个synchronized中的代码,此时就会进入“死锁”,也就是卡住,利用生活中的例子就像你把钥匙锁在了屋里一样,想要开门就需要钥匙,可是钥匙确被门锁了起来。此时想要解决这种情况,很简单,不在同一个线程中对同一把锁重复加两次就好了,或者使用可重入锁,都可以解决这种问题。

2.可重入锁 

        Java中的锁是可重入锁,在这里再对可重入锁进行一个简单介绍。

        对于可重入锁来说,它的内部会持有两个信息:

  1. 当前这个锁是哪个线程持有的
  2. 记录加锁次数的计数器

        下面再利用上面演示的代码,对Java中可重入锁的执行过程进行详细介绍:         在上述对同一把锁进行第二次加锁时,会先判断当前加锁的线程是否是持有锁的线程,如果不是同一个线程,那么就会进行阻塞,如果是同一个线程就只会进行计数器+1的操作,没有其他的操作了。由于Java中的锁是可重入锁,所以上述代码在Java中不会出现死锁的情况。

二、两个线程,两把锁

1.问题介绍

        当你创建了两个线程,线程一获取到了锁A,线程二获取到了锁B,接下来线程一尝试获取锁B,线程2尝试获取锁A,这时就会出现“死锁”,一旦出现“死锁”,线程就会“卡住了”,就无法再继续工作,这属于一个严重的bug,下面利用代码对这种情况进行演示:

public class Test2 {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(()->{
            synchronized (A) {
                try {
                    // sleep 一下,是给 t2 时间,让 t2 获取到 B
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 尝试获取 B, 但没有释放 A
                synchronized (B) {
                    System.out.println("线程 1 拿到了两把锁");
                }
            }
        });

        Thread t2 = new Thread(()->{
            synchronized (B) {
                // sleep 一下, 是给 t1 时间,让 t1 获取到 A
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 尝试获取到 A, 并没有释放 B
                synchronized (A) {
                    System.out.println("线程 2 拿到了两把锁");
                }
            }
        });

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

        运行结果如下:         此时,线程一等线程二释放锁B,线程二等线程一释放锁A,两个线程互不想让,就出现了“死锁”这种状况,这就好比,你把家的钥匙锁在了车里,然而车的钥匙却锁在了家里。

2.解决方式

        面对上述的问题,最好的解决方式就是规定加锁的顺序,我们约定先对A加锁,然后在对B加锁,此时问题就会得到解决,按照这个方案对代码进行修改,代码如下:

public class Test2 {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(()->{
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 尝试获取 B, 但没有释放 A
                synchronized (B) {
                    System.out.println("线程 1 拿到了两把锁");
                }
            }
        });

        Thread t2 = new Thread(()->{
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 尝试获取到 B, 并没有释放 A
                synchronized (B) {
                    System.out.println("线程 2 拿到了两把锁");
                }
            }
        });

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

        运行结果: 

三、N个线程,M把锁

1.哲学家就餐问题

        N个线程,M把锁这种情况有一个非常经典的问题,这就是哲学家就餐问题,下面来简单介绍一下这个问题,如下图所示:

        话说从前有五个哲学家,他们坐在桌子前,在他们两两之间放着一根筷子,这里规定每个哲学家只能拿起双手边的两根筷子, 此时当一个哲学家要吃锅包肉的时候,坐在他旁边的两个哲学家就需要阻塞等待,只有当这个哲学家吃完的时候,主动放下筷子,他旁边的两个哲学家才能拿到他手里的筷子,这里虽然筷子的数量不充裕,但是也还好,因为每个哲学家除了吃锅包肉以外还要做一件事,就是“思考人生”,这时哲学家就会放下筷子,由于每个哲学家什么时候吃锅包肉,什么时候“思考人生”是不确定的,所以这个模型在一个特殊情况下是不可以正常工作的。

        假设,在同一时刻,所有的哲学家都想吃锅包肉了,他们同时抄起了左手的筷子,这个时候他们再想拿右手的筷子,就拿不到了,因为右手的筷子被别的哲学家给拿了,此时,由于所有的哲学家都不想放下已经拿起来的筷子,就要等旁边的哲学家放下筷子,可是没有哲学家吃到锅包肉,也就没有哲学家放下筷子,这样就出现了“死锁”,如下图所示:

        在上述问题中,每个哲学家就相当于一个线程,五个筷子就相当于五把锁,哲学家们啥时候吃锅包肉,啥时候“思考人生”这属于“随机调度”,绝大部分情况下是可以正常工作,但是出现上图情况是就会出现“死锁”。

2.解决方式

        解决“死锁”的问题,方案有很多种,在解决上述问题前,先解释一下“死锁”产生的四个必要条件(全部具备才可以,缺一不可):

  1. 互斥使用:获取锁的过程是互斥的,一个线程拿到了这把锁,另一个线程也想获取这把锁就需要阻塞等待。
  2. 不可抢占:一个线程拿到了锁之后,只能主动解锁,不能让别的线程强行把锁抢走。
  3. 请求保持:一个线程拿到了锁A之后,在持有锁A的前提下尝试获取锁B。
  4. 循环等待/环路等待:这是一种代码结构。

        解决死锁问题,核心思路,破坏上述的必要条件,破坏一个就可以解决, 那么这个问题可以从哪里入手呢?首先,第一个互斥使用和第二个不可抢占,这是锁的基本特性,不好破坏,其次,第三个请求保持,这个要看实际代码与实际需求,最后,第四个循环等待,关于代码结构,破坏起来是最容易的,所以我们解决这个问题从第四个必要条件开始。

        这里我们只有指定一定的规则,就可以有效的避免循环等待,比如,我们指定加锁的顺序:

        针对五把锁,都进行编号,约定每个线程获取锁的时候,一定要先获取编号小的锁,再获取编号大的锁。

        这种解决方案,可以如下图所示(为了表示方便,这里对哲学家们也进行了编号): 

        图中由于一号哲学家获取了一号筷子,导致二号哲学家想获取编号小的筷子时只能等一号哲学家放下筷子,由于规定必须先获取编号小的筷子后获取编号大的筷子,所以二号哲学家不会获取三号筷子,这样就保证在餐桌上至少会有一位哲学家正在进餐,上述图中表示的只是一种情况,规定完拿筷子的顺序之后就解决了哲学家进餐的问题。

        解决哲学家就餐问题其实有很多种方案:

  1. 多添置一些额外的筷子
  2. 减少一个哲学家
  3. 引入计数器,限制最多多少人吃锅包肉
  4. 引入拿筷子的规则
  5. 利用“银行家算法”

        这些方案中,1~3方案虽然不复杂,但是普适性不高,在一些特定需求中不可以使用,第5个方案,确实可以解决“死锁”问题,但是个人并不推荐,因为比较复杂,容易引入新的问题。 

·结尾

        本篇文章到这就快要结束了,在进行多线程编程时,线程安全是一个让我们感到复杂的问题,“死锁”只是其中问题之一,上述的所有内容都是在对“死锁”的问题进行介绍与解决,如果文章有问题欢迎在评论区进行讨论,如果感觉文章讲述还不错的话,也希望大家能给个三连支持一下~~你们的支持就是我最大的动力,我们下一篇文章再见吧┏(^0^)┛

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

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

相关文章

plt的简单使用

目录 介绍示例 介绍 plt 是 Python 中 Matplotlib 库的一个常用别名,它表示 pyplot,这是一个用于创建图形和图形的可视化表示的工具。下面是一些 plt 函数的详解和示例,以帮助大家理解和使用。 示例 示例1: import matplotlib…

AV1 Bitstream Decoding Process Specification--[9]:语法结构语义-5

原文地址:https://aomediacodec.github.io/av1-spec/av1-spec.pdf 没有梯子的下载地址:AV1 Bitstream & Decoding Process Specification摘要:这份文档定义了开放媒体联盟(Alliance for Open Media)AV1视频编解码…

loadrunner个人笔记

创建场景配置: 两个同时 去四:日志、时间、模拟、其他自动事务 加一:首选项 1、写脚本,沟通官方、文件打印扫描 MFI-sw.support.gsd.imsc.sda.globalopentext.com support.casemicrofocus.com 支持资源 | Micro Focus | OpenT…

【毕业论文+源码】基于ASP的课程指导平台的开发

引 言 随着全球信息化技术的兴起,特别是Internet的日益普及,解决了信息Internet上传递的问题,建立了一个组织得很好的信息结构框架,使得Internet用户能够在Internet上的任何一个终端,以一种简单、统一的方式来访问超…

Leetcode Hot 100刷题记录 -Day18(反转链表)

反转链表: 问题描述: 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出&a…

视频监控相关笔记

一、QT 之 QTreeWidget 树形控件 Qt编程指南,Qt新手教程,Qt Programming Guide 一个树形结构的节点中的图表文本 、附带数据的添加: QTreeWidgetItem* TourTreeWnd::InsertNode(NetNodeInfo node, QTreeWidgetItem* parent_item) { // …

回文子串通用做法

647. 回文子串 先引出力扣链接 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。示例 1: 输入:"abc" 输出…

for循环的应用

正三角 for (int i 0; i < 8; i) { for (int j 0; j < 15; j) { int kong 8 - i - 1; int star 2 * i 1; if (j < kong) { Console.Write(" "); } else if (j < kong star)…

【LLM学习之路】9月23日24日 第十、十一天 Attention代码解读

【LLM学习之路】9月23日24日 第十、十一天 Attention代码解读 Transformer模型大致分为三类 纯 Encoder 模型&#xff08;例如 BERT&#xff09;&#xff0c;又称自编码 (auto-encoding) Transformer 模型&#xff1b;纯 Decoder 模型&#xff08;例如 GPT&#xff09;&#…

多颜色绘制语义分割/变化检测结果图

在论文绘图时&#xff0c;传统的二元语义分割结果图颜色单一&#xff08;下图左&#xff09;&#xff0c;所以论文中常根据混淆矩阵类别使用多颜色进行绘制&#xff08;下图右&#xff09;&#xff0c;可以看到&#xff0c;结果的可视化效果更好。 以下是绘制代码&#xff1a; …

同等学力申硕英语网课如何选择

很多考生想知道同等学力申硕英语网课如何选择&#xff0c;小编告诉大家&#xff0c;首先明确自己的学习目标和需求是为了提高口语、阅读、写作还是听力能力? 只有明确了自己的学习目标和需求&#xff0c;才能更好地选择适合自己的课程和平台。 二、选择知名品牌和口碑良好的平…

UE5地图白屏/过曝/非常亮の解决方法

今天遇到一个问题 , 新建项目 , 打开虚幻第三人称地图的默认关卡 , 发现白屏 , 啥也看不见 猜测可能是虚幻编辑器的bug , 造成白屏的原因应该是场景过曝了 记录一下解决方案 第一种解决方法 找到场景中的 后期处理体积 (PostProcessVolume) 直接删掉 或者找到 细节面板中 -…

衍射的角谱理论

一、单色平面波与本征函数 不考虑夫琅禾费近似, 则相干光场在给定二平面间的传播过程就是通过一个二维线性空不变系统。 上式函数是这个系统的本征函数,表示振幅为1的平面波在xy平面上的复振幅分布,空间频率分量 = cos / , = cos / 与平面波的传播方向相联系, 空间…

java和mysql命名规则不一样,每次在mybatis中起别名太麻烦?来看看如何设置自动自动映射!

简介&#xff1a; 在 Java 开发中&#xff0c;当使用 MyBatis 框架连接 Java 代码与 MySQL 数据库时&#xff0c;常常会遇到 Java 和 MySQL 命名规则不一致的问题&#xff0c;这使得每次在 MyBatis 中为查询结果起别名变得繁琐。本教程将深入探讨如何设置自动映射&#xff0c;以…

java节假日工具类,判断一个日期是否是法定节假日

java节假日工具类&#xff0c;判断一个日期是否是法定节假日 1.HolidayUtil工具类2.工具类生成的日期json文件3.结果展示 无需链接数据库&#xff0c;无需手写节假日集合列表 1.HolidayUtil工具类 import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.data…

DataGemma:谷歌大模型

诸神缄默不语-个人CSDN博文目录 DataGemma是谷歌出的大模型&#xff0c;是gemma 2的升级版&#xff0c;主要亮点是基于检索解决幻觉问题。 在huggingface和kaggle上均可下载模型权重。 检索数据源是Google’s Data Commons知识图谱。 官方博客&#xff1a;https://blog.googl…

Centos redis下载安装以及redis manager连接详细教程

一、redis下载以安装&#xff1a; 1.切换到home目录下 cd /home&#xff08;/是根目录&#xff0c;./是当前目录 ../是父目录&#xff09; 2.执行wget http://download.redis.io/releases/redis-5.0.2.tar.gz&#xff08;这里&#xff0c;不知道为什么安装6开头的版本在make的…

定制智慧科技展厅方案:哪些细节是成功的秘诀?

随着数字科技浪潮的迅猛推进&#xff0c;智慧科技展厅跃升为科技成果展示与技术对话的前沿阵地。其策划与实施方案因而显得尤为关键。在此过程中&#xff0c;精雕细琢每一环节&#xff0c;确保创意与技术的无缝对接&#xff0c;成为不可或缺的要点。现在&#xff0c;让我们深入…

CS创世8GB SD NAND的低功耗特性

在电子设备不断追求低功耗的今天&#xff0c;CS创世半导体的8GB SD NAND芯片以其低功耗特性脱颖而出。这款芯片的读写电流仅为15mA&#xff0c;相较于同类产品&#xff0c;其功耗显著降低&#xff0c;这不仅延长了设备的使用时间&#xff0c;还减少了对电池的依赖。这种低功耗特…

HDMI20协议解析_Audio_Sample

HDMI20协议解析_Audio_Sample 1.版本说明 日期作者版本说明202409XX风释雪初始版本 2.概述 当通过HDMI传输音频信号时&#xff0c;Audio_Sample是必须要传输的数据包之一&#xff1b; 通过前端硬件或软件收到PCM原始音频数据后&#xff0c;需要通过Audio_Sample packet发送给…