JavaEE 第5节 死锁产生的原因

news2024/11/16 9:33:27

目录

一、死锁场景

场景1:1个线程1个锁

场景2:2个线程2个锁

场景3:N个线程M个锁

二、出现死锁的四个必要条件

1)锁的互斥性(Mutual Exclusion)

2)锁的不可抢占性(Non-preemption)

3)请求保持(Hold and Wait)

4)循环等待(Circular Wait)

三、避免死锁的方式

1.锁的互斥性(不可干预)

2.锁的不可抢占性(不可干预)

3.请求锁时,不允许持有锁

4.打破循环依赖


一、死锁场景

为了能够加深对死锁产生原因的理解,我们先来看看产生死锁的几个经典场景:

场景1:1个线程1个锁

对这个线程进行重复上锁:

public class Threads {
    static int count = 0;

    //实现加锁
    private synchronized static void add() {
        count++;
    }
public static Object object=new Object();
    public static void main(String[] args) throws InterruptedException {
        
        Thread t=new Thread(()->{

            //第一次上锁
            synchronized (object){
                //程序会永远停留在这里
                /*第二次上锁,需要等待第二次解锁,但是第二次解锁需要等待第二次上锁成功。
                * */
                //第二次上锁
                synchronized(object){
                    
                }
            }
        });
        t.start();
        t.join();

    }

}

注意:

在JAVA的synchronized关键字中,不会出现这中情况,因为synchronized关键字会自动识别,只对同一个线程的同一个对象上锁一次。


那么synchronized是怎么完成这一操作的呢?

底层使用的是引用计数:

这样,不论加几次锁,实际的锁只有一个。

场景2:2个线程2个锁

我先用一个形象的例子说明:

假如有两个小朋友A和B分别在一个独木桥的两端,他们都要过这个一个独木桥。

当他们在独木桥相遇的时候,出现了这样一个情况,两个人都相互谦让:

最后结果就是两个人都停留在那里,都走不了。

用代码演示:

public class Threads {
    static int count = 0;

    //实现加锁
    private synchronized static void add() {
        count++;
    }
public static Object object1=new Object();
    public static Object object2=new Object();
    public static void main(String[] args) throws InterruptedException {

            Thread threadA=new Thread(()->{
                synchronized (object1){
                    System.out.println("线程A对1上锁");

                    try {
                        Thread.sleep(1000);//休眠确保线程B对1 2都上锁,然后在执行A线程接下来的程序
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(object2){
                        System.out.println("线程A在对2上锁");
                    }

                }


            });

            Thread threadB=new Thread(()->{
               synchronized(object2){
                   System.out.println("线程B对2上锁");

                   synchronized(object1){
                       System.out.println("线程B对1上锁");
                   }
               }
            });


            threadA.start();
            threadB.start();
            threadA.join();
            threadB.join();
    }

}

运行结果:

打印完这两句话后进入死循环。

注意:

出现这种死锁必须保证这两个线程是在持有一个锁的基础上,又去加另一个锁,比如A线程在上了1锁的前提下(synchronized代码块内),有取加了2的锁:

线程B,也必须如此:

        

场景3:N个线程M个锁

场景三是一个很经典的模型:

假如说下面五位都要吃到桌上的食物,每个人需要用一双筷子才行:

上面的五位哥哥就是五个线程,五支筷子就是五个不同对象的锁。

通常情况下,都能吃到里面的鸡汤(一个哥哥用完一双筷子,把筷子留给其他哥哥用),但是有时候程序运行时,会出现这样一个特殊的情况:

也就是他们同时拿其同一侧的一只筷子的时候,每一位哥哥都拿了一支筷子,但是每一个哥哥都尝不到里面的鸡汤,因为必须持有一双筷子才能尝到鸡汤的鸡肉,于是每位各个都一直干等着,这样就形成了死锁。

二、出现死锁的四个必要条件

1)锁的互斥性(Mutual Exclusion)

可以把锁想象成一个很小的单间,如果有多个线程加了这把锁,那么同一时刻只能有一个线程能够进到这个房间,既这把锁只能被一个线程持有并使用,保证持有这把锁的线程对资源独占访问,从而避免线程安全问题

这是锁的基本特性,无法干预

2)锁的不可抢占性(Non-preemption)

还是刚才的单间例子,在一个线程抢到到这个小房间的时候(持有这把锁),其他线程不能把这个线程强制拖出来自己占有

这是锁的基本特性,通常无法干预

3)请求保持(Hold and Wait)

最典型的例子就是场景1和场景2了。

与代码结构有关,可干预。

在持有一把锁且不释放的情况下,又去拿锁。

4)循环等待(Circular Wait)

顾名思义,线程之间在相互等待解锁,形成一个逻辑上的死循环。

与代码结构有关,可干预。

三、避免死锁的方式

想要解决死锁问题,只需要破坏一下四个条件中的其中一个就能成功。

1.锁的互斥性(不可干预)

锁的互斥性(Mutual Exclusion)是锁的基本特性,它是保证线程资源独占访问的关键,是无法干预的。

2.锁的不可抢占性(不可干预)

锁的不可抢占性(Non-preemption)也是锁的基本特性,它保证线程在持有资源的时候不会被其他线程打断,保证数据的一致性,所以也是不可干预的。

3.请求锁时,不允许持有锁

请求保持(Hold and Wait):这个条件我们是可以干预的。

对典型的就是上面2个线程2把锁这个例子,倘若两个线程想要拿另一把锁的时候,把当前的锁释放掉,再去拿另一把锁,死锁就不会发生:

public class Threads {
    static int count = 0;

    //实现加锁
    private synchronized static void add() {
        count++;
    }
    public static Object object1=new Object();
    public static Object object2=new Object();
    public static void main(String[] args) throws InterruptedException {

        Thread threadA=new Thread(()->{
            synchronized (object1){
                System.out.println("线程A对1上锁");

                try {
                    Thread.sleep(1000);//休眠确保线程B对1 2都上锁,然后在执行A线程接下来的程序
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //出了object1对象的锁,再去压object2的锁,其他代码不变
            synchronized(object2){
                System.out.println("线程A在对2上锁");
            }


        });

        Thread threadB=new Thread(()->{
            synchronized(object2){
                System.out.println("线程B对2上锁");

                synchronized(object1){
                    System.out.println("线程B对1上锁");
                }
            }
        });


        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
    }

}

运行结果:

注意:

这个解决方案不是万能的,因为有时候代码就是要写成保持请求的状态。

4.打破循环依赖

产生循环依赖,这个当然是可以干预的。想办法打破循环条件即可。

最简单高效的方式就是给锁编号,比如上文谈到的N个线程M把锁:

我们把五枝筷子(锁)进行编号1、2、3、4、5

规定,每一位各个拿筷子的时候只能先拿起编号小的筷子后拿起编号大的筷子

这是我们发现,循环依赖实际上已经打破了,因为因为一号哥哥不会去大号(5号)筷子,所以五号哥哥可以拿到5号筷子,等五号哥哥炫完鸡汤后,他会把4、5号筷子都放下,然后三号哥哥就可以开炫了,以此类推直到所有线程完成任务。


另外银行家算法也可以解决循环依赖的问题,但是本身这个算法就很复杂,容易出bug,所以这里就不做过多讨论了,感兴趣的同学可以自己去查阅学习。

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

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

相关文章

容器跨主机通信:Flannel网络实现机制分析

Docker与Kubernetes系列 容器跨主机通信:Flannel网络实现机制分析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of thi…

NLP——文本预处理

本文思维导图 文本预处理及其作用 文本语料在输送给模型前一般需要一系列的预处理工作, 才能符合模型输入的要求, 如: 将文本转化成模型需要的张量, 规范张量的尺寸等, 而且科学的文本预处理环节还将有效指导模型超参数的选择, 提升模型的评估指标. 一、文本处理的基本方法 1…

职业院校工业互联网平台应用实训室解决方案

摘要:随着“中国制造2025”战略的深入实施,工业互联网作为新一代信息技术与制造业深度融合的关键支撑,对我国产业升级和经济转型具有重要意义。为培养适应未来工业发展需求的技术技能人才,职业院校需要建立工业互联网平台应用实训…

Python爬虫新手指南及简单实战

网络爬虫是自动化获取网络信息的高效工具,Python因其强大的库支持和简洁的语法成为编写网络爬虫的首选语言。本教程将通过一个具体的案例(基于Microsoft Edge浏览器的简单爬取),指导你使用Python实现一个完整的网络爬虫&#xff0…

群晖出现grub-editenv:error:environment block too small

起因: 想修改一下东西,结果找到配置修改后出现grub2-editenv: error: environment block too small. ,然后就不能启动了 原因是: 修改的部分导致启动环境检测到内存分配的较小 解决 1.切换管理用户 sudo -i 2.找文件并备份 find / -name gru…

基于C#调用文心一言大模型制作桌面软件(可改装接口)

目录 开发前的准备账号注册应用创建应用接入 开始开发创建项目设计界面使用 AK,SK 生成鉴权签名窗体代码 百度智能云千帆大模型平台什么是百度智能云千帆大模型平台模型更新记录 开发前的准备 账号注册 访问百度智能云平台,通过百度账号登录或手机号验证…

边缘计算在智能交通系统中的应用探究

随着社会的进步和科技的不断发展,智能交通系统正逐渐成为现代城市交通管理的重要组成部分。边缘计算作为一种新兴的计算模式,被广泛应用于智能交通系统中,为其带来了巨大的变革和改进。本文将从边缘计算的原理、优势以及在智能交通系统中的应…

61监听器

监听器 概念 监听器用于监听web应用中某些对象信息的创建、销毁、增加,修改,删除等动作的 发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用 监听器对象中的方法。 常用于统计在线人数和在线用户&am…

Hbase 综合实验

1、设计表保存通话记录信息,实现相关查询功能 创建ua50tel表,并插入数据(实训报告中‘:’存在于表中但会识别为语法所以在此不使用) 依次重复上面的过程按实际输入,并查看表的内容 查询某人当月通话记录 …

内网电脑如何通过外网IP访问内网的服务器

内网的电脑,如何使用公网IP访问内网的服务器?写下这个标题,连我自己都学得拗口,可是又想不到更合适的标题。 正常来说,内网的电脑访问内网的服务器,当然是使用服务器的内网IP地址和端口,比如说&…

建立的Qsys系统变成Symbol Files

1.Platform Designer下,点击Generate 2.

C++数组全面讲解

文章目录 数组介绍数组的特点数组的优缺点数组和其他数据结构的比较 静态数组与动态数组的详细讲解一、静态数组1. 一维数组声明与初始化注意事项特点 2. 二维数组声明与初始化注意事项特点 3. 三维数组声明与初始化注意事项特点 二、动态数组1. 一维数组声明与初始化注意事项特…

HarmonyOS鸿蒙开发学习:鸿蒙基础-基础环境-ArkTS-组件-样式

鸿蒙基础-基础环境-ArkTS-组件-样式 DevEcoStudio编辑器下载链接 链接: https://pan.baidu.com/s/18C9i35YPh4GsHpbSif8KQw?pwdd6e9 提取码: d6e9 安装教程 下载对应的版本 windowsmac英特尔mac Arm API12 的编辑器 API12的模拟器(mac英特尔安装API11) Mac Arm芯片版本安装…

CCPC 2024, Shanghai

2024.8.5 12:00————17:00 CCPC 2024, Shanghai [A - 无线网络整点栅格统计](https://atcoder.jp/contests/math-and-algorithm/tasks/abc204_d?langen)[E - 无线软件日](https://atcoder.jp/contests/abc265/tasks/abc265_a?langen)[J - 极简合数序列](https://atcoder.…

【面试题】合井K个升序链表

合井K个升序链表 仅供面试学习 给定一个链表数组,每个链表都已经按升序排列。将所有链表合并到一个升序链表中,并返回合并后的链表。以下是详细的解题步骤和 Python 代码示例。 一、问题描述 将多个升序链表合并为一个升序链表。要求算法能有效处理链表…

Codeforces Round 871 (Div. 4)(A~H)

比赛链接 Dashboard - Codeforces Round 871 (Div. 4) - Codeforces A. Love Story 找到与codeforces 有多少个不同的字符。 #include<bits/stdc.h> #define int long long #define TEST int T; cin >> T; while (T--) #define ios ios::sync_with_stdio(fals…

人大金仓安装图文

1.下载 通过百度网盘分享的文件&#xff1a;人大金仓安装图文 链接&#xff1a;https://pan.baidu.com/s/1imt0KsyVXQALp_icEMoDpQ 提取码&#xff1a;zwef --来自百度网盘超级会员V3的分享 2.安装

CTFHub~RCE远程代码执行

0X01eval执行 # 进来我们直接看到了代码&#xff0c;大致意思就是传入一个参数cmd# 所以我们直接使用蚁剑进行连接&#xff0c;看一下能否成功# 接下来我们直接找出flag0X02文件包含 # 打开页面发现一大堆代码 大体意思是如果file中没有flag字符串就执行下面的include $_GET[…

【linux深入剖析】线程控制 | 多线程

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. 创建线程2. POSIX线程…

基于Kubernetes v1.25.0和Docker部署高可用集群(说明篇)

目录描述 Kubernetes组件说明 特定接口CRI、CNI、CSI Kubernetes v1.25集群创建方案选择 一、Kubernetes组件说明 Docker 的运行机制 运行机制详解&#xff1a; docker client&#xff1a;命令行输入的docker命令就是一个docker客户端 docker Engine&#xff1a;Engine也…