美团面试:有哪些情况会产生死锁

前言

我们首先需要知道,死锁一定发生在并发场景中。为了保证线程安全,有时会给程序使用各种能保证并发安全的工具,尤其是锁,但是如果在加解锁过程中处理不恰当,就有可能适得其反,导致程序出现死锁的情况。

什么是死锁

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

Java应用程序死锁产生的四个必要条件:
  1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。
  2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3、请求和保持,即当资源请求者在请求其它资源的同时保持对原有资源的占有。
  4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待闭环。

当上述四个条件都成立的时候,便形成死锁。当然,如果打破上述任何一个条件,便可终结死锁。

解决死锁问题的方法常用的有三种:一种是用synchronized,一种是用Lock显式锁实现,另一种是在分布式环境中加分布式锁。

死锁是一种状态,当两个(或多个)线程(或进程)相互持有对方所需要的资源,却又都不主动释放自己手中所持有的资源,导致大家都获取不到自己想要的资源,所有相关的线程(或进程)都无法继续往下执行,在未改变这种状态之前都不能向前推进,我们就把这种状态称为死锁状态,认为它们发生了死锁。简而言之,死锁就是两个或多个线程(或进程)被无限期地阻塞,相互等待对方手中资源的一种状态。

图片

图1 两个线程死锁的情况

如图所示,线程1已经持有了锁1,同时线程2也已经持有了锁2,然后线程1尝试获取锁2,但是线程2并没有释放锁2,所以线程1处于阻塞状态;同理可得,线程2获取锁1也会被阻塞。由此可见,线程1和线程2就发生了死锁,因为它们都相互持有对方想要的资源,却又不释放自己手中的资源,形成相互等待,而且会一直等待下去。

另外:
推荐一个程序员免费学习的编程网站:我爱编程网(www.love-coding.com)
涵盖 Java几乎覆盖了所有主流技术面试题,还有市面上最全的技术精品系列教程,免费提供。
在这里插入图片描述

2 死锁的影响和危害

2.1 死锁的影响

死锁的影响在不同系统中是不一样的,影响的大小一部分取决于当前这个系统或者环境对死锁的处理能力。

2.1.1 数据库中

例如,在数据库系统软件的设计中,考虑了监测死锁以及从死锁中恢复的情况。在执行一个事务的时候可能需要获取多把锁,并一直持有这些锁直到事务完成。在某个事务中持有的锁可能在其它事务中也需要,因此在两个事务之间有可能发生死锁的情况;一旦发生了死锁,如果没有外部干涉,那么两个事务就会永远的等待下去。

但数据库系统不会放任这种情况发生,当数据库检测到这一组事务发生了死锁时,根据策略的不同,可能会选择放弃某一个事务,被放弃的事务就会释放掉它所持有的锁,从而使其它的事务继续顺利进行。此时程序可以重新执行被强行终止的事务,而这个事务现在就可以顺利执行了,因为所有跟它竞争资源的事务都已经在刚才执行完毕,并且释放资源了。

2.1.2 JVM 中

在 JVM 中,对于死锁的处理能力就不如数据库那么强大了。如果在 JVM 中发生了死锁,JVM 并不会自动进行处理,所以一旦死锁发生,就会陷入无穷无尽的等待。

2.2 死锁的危害以及特点

死锁和其它的并发安全问题一样,是概率性事件,也就是说,即使存在发生死锁的可能性,也并不是 100% 会发生的。如果每个锁的持有时间很短,那么发生冲突的概率就很低,所以死锁发生的概率也很低。但是在线上系统里,可能每天有几千万次的“获取锁”、"释放锁”操作,在巨量的加解锁过程中,整个系统发生问题的概率就会被放大,只要有某几次操作是有风险的,就可能会导致死锁的发生。

也正是因为死锁“不一定会发生”的特点,导致提前找出死锁成为了一个难题。压力测试虽然可以检测出一部分可能发生死锁的情况,但是并不足以完全模拟真实、长期运行的场景,因此没有办法把所有潜在可能发生死锁的代码都找出来。

死锁还会导致产生新的死锁,像滚雪球一样,越滚越大。其它线程因请求不到死锁进程已占用的资源而无法向前推进,也会发生死锁。所以如果线程出现了死锁,有可能产生多米诺骨牌效应,最终会导致操作系统崩溃。

一旦发生了死锁,根据发生死锁的线程的职责不同,就可能会造成 子系统崩溃、性能降低甚至整个系统崩溃等各种不良后果。而且死锁往往发生在高并发、高负载的情况下,因为可能会直接影响到很多用户,造成一系列的问题。以上就是死锁发生几率不高但是危害大的特点。

3 形成死锁的四个必要条件

1.互斥使用,获取锁的过程是互斥的。一个线程拿到了一把锁,另一个线程也想获取这把锁,就需要阻塞等待。

2.不可抢占。一个线程拿到了锁之后,只能主动解锁,不能让别的线程强行把锁抢走。

3.请求保持。一个线程拿到了锁A之后,在持有A的前提下,尝试获取B。

4.循环等待:存在一个进程链,每个进程都在等待下一个进程所占有的资源。

4 形成死锁的三种情况

第一种情况:

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

如果不是不可进重入锁。会出现下面的情况

举例:
package 多线程;
//死锁
public class ThreadDemo15 {
  public static void main(String[] args) {
      Object locker =new Object();
      Thread t = new Thread(()-> {
          synchronized (locker) {
              synchronized (locker) {//当前由于事同一个线程,此时锁对象,就知道第二次加锁的线程,就是持有锁的线程。第二次操作会直接放行。
                  System.out.println("hello");
              }
          }//在这里解锁
      });
      t.start();
  }
}

它就会打印一个hello。

图片

如果是不可重进入锁

举例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockExample {
  private static Lock lock = new ReentrantLock();
   
  public static void main(String[] args) {
      Thread thread = new Thread(() -> {
          lock.lock();
          System.out.println("Thread is holding the lock");

          // 尝试再次获取锁,会导致死锁
          lock.lock();

          System.out.println("This line will not be reached");
          lock.unlock();
      });

      thread.start();
  }
}

他就不会输出东西。

第二种情况:两个线程 两把锁

线程1 获取到 锁A

线程2 获取到 锁B

接下来,1 尝试获取B,2尝试获取A ,就会出现死锁。

一旦出现死锁,线程就会被卡住无法继续工作。

举例:

package 多线程;
//死锁
public class ThreadDemo16 {
  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) {
                  e.printStackTrace();
              }
              //A尝试获取B,并没有释放A
              synchronized (B){
                  System.out.println("t1 拿到了B");
              }
          }
      });
      Thread t2 = new Thread(()->{
          synchronized (B){//约定加锁顺序,
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              synchronized (A){
                  System.out.println("t2 拿到了A");
              }
          }
      });
      t1.start();
      t2.start();
  }
}

当我们输出结果就会发现它一直没有输出任何东西。当t1线程持有A的锁资源时,它尝试获取B,而同时t2线程持有B的锁资源,它尝试获取A,这样两个线程相互等待对方的锁资源,导致死锁的情况发生。

图片

如何去解决这个问题呢,关键就在于死锁形成的四个必要条件,只要我们可以打破这四个必要条件,就不会形成死锁。这个题,我们约定好加锁的顺序的话,就不会出现死锁。

  package 多线程;
//死锁
public class ThreadDemo16 {
  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) {
                  e.printStackTrace();
              }
              //A尝试获取B,并没有释放A
              synchronized (B){
                  System.out.println("t1 拿到了B");
              }
          }
      });
      Thread t2 = new Thread(()->{
          synchronized (A){//约定加锁顺序,
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              synchronized (B){
                  System.out.println("t2 拿到了A");
              }
          }
      });
      t1.start();
      t2.start();
  }
}

当t1线程获取到A对象的锁资源后,它会尝试获取B对象的锁资源,但是此时B对象已经被t2线程锁住了,因此t1线程会进入等待状态。当t2线程获取到B对象的锁资源后,它会尝试获取A对象的锁资源,此时A对象没有被锁住,因此t2线程可以获取到A对象的锁资源,执行完成后释放锁资源,然后t1线程才能继续执行,获取B对象的锁资源,避免了死锁的发生。

第三种情况:N个线程 M把锁

哲学家进餐问题

图片

描述了五位哲学家围坐在一张圆桌旁,每个人面前都有一碗米饭和一只筷子。这些哲学家只能用左手和右手各拿一只筷子进食。问题是,如何安排他们的动作,使得每个哲学家都能进餐?

问题分析 :

由问题描述我们可以知道,一共有五个哲学家,也就是五个进程;五只筷子,也就是五个临界资源;因为哲学家想要进餐,必须要同时获得左边和右边的筷子,这就是要同时进入两个临界区(使用临界资源),才可以进餐。

问题解决:

一次只允许两个哲学家进餐,并且要求他们都拿到右手边的筷子后才能开始进食。引入一个仲裁者,即一个额外的实体负责协调哲学家的动作,以避免死锁的发生。使用资源分配算法,例如Dijkstra的银行家算法,来确保每个哲学家都能有足够的资源进餐。

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

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

相关文章

如何在Linux上安装Canal同步工具

1. 下载安装包 所用到的安装包 canal.admin-1.1.4.tar.gz 链接:https://pan.baidu.com/s/1B1LxZUZsKVaHvoSx6VV3sA 提取码:v7ta canal.deployer-1.1.4.tar.gz 链接:https://pan.baidu.com/s/13RSqPinzgaaYQUyo9D8ZCQ 提取码:…

百度主动推送可以提升抓取,它能提升索引量吗?

站长在建站SEO的时候,需要用到百度站长平台(资源平台)的工具,在站长工具中【普通收录】-【资源提交】-【API提交】这个功能,对网站的抓取进行一个提交。 这里估计很多站长就有疑问,如果我主动推送&#xf…

如何将Latex的文章内容快速用word+Endnote排版

1 第一步 Endnote文件是无法直接导入bib文件的。需要将reference.bib的参考文献内容,通过JabRef软件打开并另存为refefence.ris文件 下载JabRef软件:https://www.jabref.org/#download 导出为ris格式文件 2 第二步 通过Endnote导入ris文件&#xff0…

Telegram bot Mini-App开发实践---Telegram简单介绍与初始化小程序获取window.Telegram.WebApp对象并解析

➡️【好看的灵魂千篇一律,有趣的鲲志一百六七!】- 欢迎认识我~~ 作者:鲲志说 (公众号、B站同名,视频号:鲲志说996) 科技博主:极星会 星辉大使 后端研发:java、go、python、TS,前电商、现web3 主理人:COC杭州开发者社区主理人 、周周黑客松杭州主理人、 AI爱好…

Hadoop 系列 MapReduce:Map、Shuffle、Reduce

文章目录 前言MapReduce 基本流程概述MapReduce 三个核心阶段详解Map 阶段工作原理 Shuffle 阶段具体步骤分区(Partition)排序(Sort)分组(Combine 和 Grouping) Reduce 阶段工作原理 MapReduce 应用场景Map…

英文版本-带EXCEL函数的数据分析

一、问题: 二、表格内容 三、分析结果 四、具体的操作步骤: 销售工作表公式设计与数据验证 类别(Category)列公式: 在Category列(假设为D列),根据ProductCode在Catalogue工作表中查找…

Jmeter数据库压测之达梦数据库的配置方法

目录 1、概述 2、测试环境 3、数据库压测配置 3.1 安装jmeter 3.2 选择语言 3.3 新建测试计划 3.4 配置JDBC连接池 3.5 配置线程组 3.6 配置测试报告 3.7 执行测试 1、概述 Jmeter是Apache组织开发的基于Java的压力测试工具,用于对软件做压力测试。 它最…

Cmakelist.txt之win-c-udp-client

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(c_udp_client LANGUAGES C) ​ add_executable(c_udp_client main.c) ​ target_link_libraries(c_udp_client wsock32) ​ ​ include(GNUInstallDirs) install(TARGETS c_udp_clientLIBRARY DESTINATION $…

02:spring之AOP

一:AOP 简介 1:AOP的概念 AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是…

商业物联网:拥抱生产力的未来

在现代商业格局中,数据占据至高无上的地位。物联网(IoT)站在这场数字革命的前沿,将以往模糊不清的不确定因素转变为可衡量、可付诸行动的深刻见解。物联网技术为日常物品配备传感器与连接功能,使其能够实时收集并传输数…

多摩川编码器协议及单片机使用

参考: https://blog.csdn.net/qq_28149763/article/details/132718177 https://mp.weixin.qq.com/s/H4XoR1LZSMH6AxsjZuOw6g 1、多摩川编码器协议 多摩川数据通讯是基于485 硬件接口标准NRZ 协议,通讯波特率为2.5Mbps 的串行通讯,采用差分两…

从壹开始解读Yolov11【源码研读系列】——Data.build.py:YOLO用于训练Train + 验证Val的无限数据集加载器DataLoader搭建

【前情回顾】在上一篇文章记录了YOLO源码data目录下的dataset.py 文件中定义的YOLO数据集类——Class YOLODataset,其基于了数据集基类BaseDataset,并重写了关键的数据预处理函数方法。 YOLODataset数据集类博文地址:Data.dataset.py&#xf…

爬虫重定向问题解决

一,问题 做爬虫时会遇到强制重定向的链接,此时可以手动获取重定向后的链接 如下图情况 第二个链接是目标要抓取的,但它是第一个链接重定向过去的,第一个链接接口状态也是302 二,解决方法 请求第一个链接&#xff0…

ssh无法连接Ubuntu

试了多次ssh都无法连接,明明可以上网 网卡、防火墙、端口都没有问题,就是连接不上 结果是这个版本Ubuntu镜像默认没有安装ssh服务 安装SSH服务:apt-get install openssh-server 开启SSH服务:/etc/init.d/ssh start 就可以连接…

I.MX6U 裸机开发18.GPT定时器实现高精度延时

I.MX6U 裸机开发18.GPT定时器实现高精度延时 一、GPT定时器简介1. GPT 功能2. 时钟源3. 框图4. 运行模式(1)Restart mode(2)Free-Run Mode 5. 中断类型(1)溢出中断 Rollover Interrupt(2&#x…

亚马逊IP关联是什么?我们该怎么解决呢?

亚马逊不仅提供了广泛的商品和服务,也是许多企业和个人选择的电子商务平台。然而,与亚马逊相关的IP关联问题,特别是在网络安全和运营管理方面,经常成为使用亚马逊服务的用户和商家关注的焦点。通过了解亚马逊IP关联的含义、可能的…

摄影:相机控色

摄影:相机控色 白平衡(White Balance)白平衡的作用: 白平衡的使用环境色温下相机色温下总结 白平衡偏移与包围白平衡包围 影调 白平衡(White Balance) 人眼看到的白色:会自动适应环境光线。 相…

Python模块、迭代器与正则表达式day10

1、Python模块 1.1模块的简介 在编写代码的时候,创建的.py文件就被称为一个模块 1.2模块的使用 想要在a文件里使用b文件的时候,只要在a文件中使用关键字import导入即可 1.2.2 from ...import...语句 导入模块可以使用import,如果只导入模…

鸿蒙系统下使用AVPlay播放视频,封装播放器

鸿蒙系统下使用AVPlay开发一款视频播放器流程 一. 申请权限 申请相关权限&#xff0c;主要是读取存储卡权限&#xff0c;方便后面扫描视频用&#xff1a; getPermission(): void {let array: Array<Permissions> [ohos.permission.WRITE_DOCUMENT,ohos.permission.REA…

城电科技|太阳能折叠灯:点亮你的便捷之光

朋友们&#xff0c;今天要给你们介绍一款能让生活变得更加美好的神器 —— 太阳能折叠灯&#xff01; 【超便捷折叠设计】 它就像一个百变精灵&#xff0c;轻松折叠起来后小巧玲珑。可以随意塞进背包的缝隙&#xff0c;或者放在车载储物箱里&#xff0c;完全不占地方&#xff…