【JavaEE初阶】死锁问题

news2024/11/25 10:09:49

目录

 一、死锁的三种典型场景

1、一个线程,一把锁

2、两个线程,两把锁

3、N个线程,M把锁


死锁,是多线程代码中的一类经典问题。我们知道加锁是能解决线程安全问题的,但是如果加锁的方式不当,就可能产生死锁。

 一、死锁的三种典型场景

1、一个线程,一把锁

对于不可重入锁来说

一个线程没有释放锁, 然后又尝试再次加锁。
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待.
lock();
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第二个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会死锁。

对于这种情况就是,如果锁是不可重入的锁,并且一个线程对这把锁加锁两次,就会出现死锁。

2、两个线程,两把锁

假设有两个线程,线程1获取到锁A(对A对象加锁),线程2获取到锁B(对B对象加锁),接下来,线程1尝试获取锁B,线程2尝试获取锁A,这时就会出现死锁。

示例代码如下:

public class ThreadDemo1 {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(()->{
            synchronized (A) {
                //sleep一下,让t2能拿到B
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //在持有A的情况下,对B加锁
                synchronized (B) {
                    System.out.println("t1拿到了两把锁");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (B) {
                //sleep一下,让t1能拿到A
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //在持有B的情况下,对A加锁
                synchronized (A) {
                    System.out.println("t2拿到了两把锁");
                }
            }
        });

        //死锁,一直僵持着,若让t2先对A加锁,再对B加锁,就可避免死锁
        t1.start();
        t2.start();

    }
}

这时候发生死锁,线程就“卡住了”,无法继续工作(死锁属于程序中最严重的一类bug),可见这个代码不会运行出任何结果,如图:

我们可以通过 jconsole 来观察这两个线程此时的状态:

 

这两个线程都处于死锁阻塞的状态。

如果此时约定加锁顺序,让线程2也先对A加锁,后对B加锁,这样死锁就可以解决。

3、N个线程,M把锁

这里用一个典型的例子来描述,哲学家就餐问题。

哲学家就餐问题:该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。

 

这个问题中,五个哲学家就相当于五个线程,五只筷子就相当于五把锁。

在绝大多数情况下,这个问题是可以正常工作的。但有一些极端的特殊情况,这时会产生死锁。假如同一时刻,所有的哲学家都想吃面条,同时拿起了自己左边的筷子,这个时候,他们继续尝试拿起右边的筷子,这时就拿不到了,因为被别人给拿着了。此时,哲学家们都等待旁边的人释放筷子,但由于所有的哲学家都不想放下自己手中的筷子,这时就产生死锁了,谁都吃不到面条。

如何解决死锁呢?我们先来看一下产生死锁的四个必要条件

  • 互斥使用:获取锁的过程是互斥的,一个线程拿到了这把锁,另一个线程也想获取,就要阻塞等待。
  • 不可抢占:一个线程拿到这把锁后,只能主动解锁,不能让别的线程强行把锁抢走。
  • 请求保持:一个线程拿到了一把锁后,会持有这把锁,像持有锁A的情况下,尝试获取锁B。
  • 循环等待/环路等待:像哲学家问题一样,1号哲学家等待2号哲学家释放筷子,2号哲学家等待3号哲学家释放筷子……

解决死锁的问题,核心思路就是破坏上述的必要条件,只要破坏一个就可以解决死锁。

对于互斥使用和不可抢占,这是锁的最基本特性,不太好破坏;对于请求保持(代码结构方面),是否能破坏,要看实际需求;对于循环等待(代码结构方面),是最容易破坏的。

解决上述哲学家问题死锁的情况,其实有很多方案:

  1. 引入额外的筷子
  2. 去掉一个线程(哲学家)
  3. 引入计数器,限制最多同时有几个哲学家进餐
  4. 引入加锁顺序的规则
  5. 银行家算法(操作系统中的重要内容)

 1、2、3方案,虽然不复杂,但是普适性不高,有时候用不了;3方案普适性高。

我们这里用一下3方案(引入加锁顺序的规则)来解决哲学家死锁的问题,只要指定一定的规则,就可以有效避免循环等待,从而破坏循环等待这个必要条件指定加锁顺序,针对五只筷子,进行编号,约定每个哲学家获取筷子的时候,一定要先拿自己左右两边编号较小的筷子,拿到之后,再拿编号较大的。如图(假设2号哲学家先拿到1号筷子):

 

5号哲学家拿到5号筷子后,就可以进餐了,吃完之后,放下4号和5号筷子;接着,4号哲学家就可以拿到4号筷子,进餐,吃完之后,放下3号和4号筷子;接着,3号哲学家就可以拿到3号筷子,进餐,吃完之后,放下2号和3号筷子……最终,1号哲学家就可以拿到1号筷子和5号筷子,进餐。这样每个哲学家都可以吃到面条,死锁问题也就解决了。

 

 

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

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

相关文章

力扣:185. 部门工资前三高的所有员工(Python3)

题目: 表: Employee ----------------------- | Column Name | Type | ----------------------- | id | int | | name | varchar | | salary | int | | departmentId | int | ----------------------- id 是该表的主键列(具…

Condition 源码解析

Condition 源码解析 文章目录 Condition 源码解析一、Condition二、Condition 源码解读2.1. lock.newCondition() 获取 Condition 对象2.2. condition.await() 阻塞过程2.3. condition.signal() 唤醒过程2.4. condition.await() 被唤醒后 三、总结 一、Condition 在并发情况下…

linux 之iptables

1.iptables防火墙基本介绍 Linux系统的防火墙:IP信息包过滤系统,它实际上由两个组件 netfilter和 iptables 组成。 主要工作在网络层,针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上。 iptables由软件包iptables提供的命令…

诊所电子处方范例之配方模板如何添加到处方中操作教程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 诊所门诊在开电子处方的时候,如果能使用模板一键导入,则可以节…

8个程序员常用的开发工具,各位老铁请收好!

Chat GPT的升级节奏让人们越来越惊讶的同时,也让大家感觉到了压力,在如此快节奏的互联网世界中,开发人员需要不断学习与更新知识,保持领先地位并高效地交付高质量软件。 无论是集成开发环境 (IDE)、版本控制系统、测试工具、协作…

二阶常系数非齐次线性微分方程:类型二

或 (1) (2) 欧拉公式 (3) 设(3)有特解 是(1)的特解 是(2)的特解 例 : 解 所以其通解为 这是属于上一篇提到的第一种情况 其特解 即 简化 -2a1 a-1/2 原方程特解为 原方程通解为 解法二 将 或 改写为 设是特征方程的k重根&a…

VR特警野外武装仿真虚拟训练实操教学保证训练效果

特警VR模拟仿真训练软件的优势主要体现在以下几个方面: 真实感和沉浸感:通过VR技术,特警可以在虚拟环境中体验真实的训练场景,如人质解救、反恐行动等。这种真实感和沉浸感可以帮助特警更好地理解和适应实际情况,提高训…

双指针算法(题目与答案讲解)

文章目录 题目移动零复写零两数之和N数之和(>2个数) 答案讲解移动零复写零两数之和N数之和 题目 力扣 移动零 1、移动零:题目链接 复写零 2、复写零:题目链接 两数之和 3、两数之和题目链接 N数之和(>2个数) 4、N数之和(三个数、四个数) 三个数:题目链接 四个数题目链接…

el-table合并行

需求: 1、”用户任务“中的”代码“需要按照升序进行排列; 2、”用户任务“中连续的”会签“是共用一个序号,并且序号进行合并 效果 解决方法 列表排序一般是前端传值,后端进行排序。由于后端返回的表格列表没有 序号indexNum …

【华为数通HCIP | 网络工程师】821刷题日记-BFD和VRRP 及重点(2)

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

jmeter多个接口测试

针对接口文档,进行对应接口设计,多个接口设计用例需要使用事物控制器。 1.通过登录接口提取sign值 发送一个登录请求,然后通过正则表达式提取该sign值 正则表达式的使用,我稍后会在下一个博文中详细说明,这边就不多说…

算法中的时间复杂度,空间复杂度

一、前言 算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别 衡量不同算法之间的优劣主要是通过时…

量子芯片:引领计算技术的新篇章

量子芯片:引领计算技术的新篇章 引言 随着量子计算的飞速发展,量子芯片作为量子计算机的核心组件,日益受到人们的关注。量子芯片的出现,不仅有望推动计算技术的革新,更将在信息安全、药物研发、金融投资等领域掀起巨大的变革。在本篇博客中,我们将深入探讨量子芯片的原理…

windows环境下载安装Nginx并配置防火墙

1、下载Nginx Nginx官网 下载稳定版 2、下载之后,解压 3、启动Nginx,命令:start nginx 最小化该窗口 主要,不要关闭,如果关闭,表示nginx服务关闭了 4、测试是否启动成功 在浏览器中输入http://localhos…

2023.11.26使用opencv调节图片亮度

2023.11.26使用opencv调节图片亮度 测试一些opencv对图片的处理效果,方法比较简单,找出所有像素点,然后将RGB三色的亮度分别进行调节即可,同类可以进行像素级的处理。测试结果和项目代码如下: 使用OpenCV调节图拍亮…

测试必会+面试必问--fiddler参数详解

Fidder抓包是作为测试必须掌握的一项技能。 这篇详细介绍(保姆级)Fiddler在测试中常用的功能。 全文较长(6000字),建议先收藏,需要时再食用。 一、界面简介 先看一下完整的Fiddler界面布局 Fiddler界面…

微信发红包,有哪些测试点

1、功能 1.在红包钱数,和红包个数的输入框中只能输入数字 2.红包里最多和最少可以输入的钱数 200 0.01 3.拼手气红包最多可以发多少个红包 100 3.1超过最大拼手气红包的个数是否有提醒 4.当红包钱数超过最大范围是不是有对应的提示 5.当发送的红包个数超过…

Kubernetes技术与架构-安全性

本文主要从不同层面与多个维度描述Kubernetes技术与架构的安全性。 云原生的安全性 从系统分层架构的角度分析,自底向上,云原生的安全性主要包括云、集群、容器以及代码四个层面,简称云原生4C安全,其架构图如下所示:…

express+multer实现简单的文件上传功能

expressmulter实现简单的文件上传功能 1.安装multer和uuid依赖 cnpm install -S uuid multer2.添加multer的配置文件 在config文件夹下添加uploa.js文件,内容如下: // 引入multer const multer require(multer) // uuid : 用于生成不重复的由英文组…

第20 章 多线程

20.1线程简介. 20.2创建线程 2.1继承Thread类 Thread 类是java.lang包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下: public Thread():创建一个新的线程对象。 public Threa…