死锁问题【javaEE初阶】

news2024/11/24 16:39:53

什么是死锁?
        所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。 

一、"死锁"出现的典型场景

(一)一个线程一把锁连续加锁两次,如果锁是不可重入锁,就会死锁

一个线程一把锁,这个就是之前所介绍的 可重入锁 

线程A 针对锁1 连续加锁两次,就构成了死锁 !

第一次加锁 加锁成功,第二次加锁 就需要第一次的锁释放,于是就阻塞等待;

但是,第一次加锁释放,就得依赖第二次加锁成功;

于是,"死锁" 就出现了!!!

(二)两个线程两把锁,相互获取对方锁 

此时,不管是不是可重入锁,都会造成 "死锁" 问题 !

可以用一个例子来说明情况:

假设 甲同学 和 乙同学 在一起

甲同学 吃饺子喜欢蘸酱油,乙同学 吃饺子喜欢蘸醋,由于在一起了,生活习性都受到了彼此的影响,两位同学吃饺子的时候 喜欢都蘸上一点了

有一天,吃饺子的时候,甲同学拿起了酱油,乙同学拿起了醋 ~

甲同学说:"你把醋给我,我用完了都给你";乙同学说:"你把酱油给我,我用完了就把醋给你" ~

此时,两者相持不下,这就造成了 "死锁" 问题 

此时,甲同学 和 乙同学 就可以看成是两个线程,酱油和醋 就可以看成是两把锁 

线程1 获取到锁A,线程2 获取到锁B;

线程1 尝试获取锁B(需要线程2 释放锁B) ,线程2 尝试获取到锁A(需要线程1 释放锁A);

在这种情况下,逻辑上就构成了循环,就构成了 "死锁" ~ 

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println( "t1:已经获取到锁1");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println( "t1:获取到锁2");
                }
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println( "t2:获取到锁2");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println(  "t2:获取到锁1");
                }
            }
        }, "t2");
        t2.start();
    }
}

 一直等待,死锁了

(三)多个线程多个锁,"哲学家就餐问题" 

此时,这种情况和 第二种情况 类似,只是更复杂一点而已 

在多个线程多把锁的情况下,"死锁"问题 就是一个概率性的问题,但是也绝对不能忽视 ! 

在谈到 "多个线程多把锁" 的时候,就会引出一个很经典的问题 —— "哲学家就餐问题" ! 

故事背景:

有 5 个哲学家,相当于有 5 个线程,他们只会做两件事情:

        思考人生(相当于是 线程休眠)
        吃面条(相当于是 在CPU上运行)
由于多线程的调度是无序的,所以说 这几个哲学家 什么时候去思考人生,什么时候去吃面条,我们是不确定的  

但是,此时 在餐桌上 一共只有 5 根筷子,并且 这5 根筷子 分别在 两两哲学家 之间 

并且,他们之间都不相互嫌弃,吃面条的时候要拿起 左右手两双筷子(这就导致相邻的哲学家需要等待) ~

此处的筷子就视为 两把"锁",只有这两把锁都获取到了,才可以吃面条 ~ 

出现 "死锁" 问题的情况:

假设 在同一时刻,所有的哲学家都想吃面条 ~

他们同时伸出左手,拿起左边的筷子;然后又同时伸出右手,尝试去拿右边的筷子;此时,右手的筷子都拿不起来,因此都无法吃面 

由于哲学家都非常固执,导致即使吃不到面条 也不会放下左手的筷子,这样的情况就会一直持续下去  ,这就构成了死锁  


 二、产生 "死锁" 的必要条件

鉴于 "死锁"问题,程序员大佬们 总结了 4 个 "死锁"产生的必要条件:

  1. 互斥使用(线程1 拿到锁A,其他线程就无法获取到 A)
  2. 不可抢占(线程1 拿到锁A,其他线程只能阻塞等待,等到线程1 主动释放锁,而不是强行把锁抢走)
  3. 请求和保持(当线程1 拿到锁A 之后,就会一直持有这个获取到锁的状态,直到说主动释放锁) 
  4. 循环等待(线程1 等待线程2,线程2 又尝试等待线程1)

三、解决 "死锁" 问题的办法

  1. 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  2. 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  3. 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  4. 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件) 

根据 产生 "死锁" 的必要条件,我们可以知道,前三个必要条件 都是在描述锁的基本特点,在实际情况下 我们并不好直接去破坏 

但是,第四个必要条件 却是和代码编写密切相关  

如果我们能够在编码上做出一些注意和约定,就可以打破 "循环等待",避免死锁 !!!

1、以确定的顺序获得锁

         针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生。

针对多把锁,进行编号:1、2、3、4、......

并且约定在获取多把锁的时候,要明确获取锁的顺序是 从小到大(或者 从大到小) 的顺序 

如(此处以从小到大为例):

线程要拿到 1、2 这两把锁,就先获取 1,再获取 2;

线程要拿到 2、4 这两把锁,就先获取 2,再获取 4 ~

只要所有的线程都遵循这个顺序,就不会出现 "循环等待",就不会出现死锁 !!!  

我们可以把这个解决办法 带入到上面的 "哲学家就餐问题" 看看 ~

约定:获取所得顺序是:从小到大 ~

 

 之后,最左边的哲学家 就可以得到两把锁了,于是就可以吃到面条了 ~

等到 吃完面条之后,会释放 4、5两把筷子;然后 最上面的哲学家 也可以吃到面条了,......,就这样的话,顺时针旋转,依次 5 位哲学家都可以吃上面条了 

2、超时放弃

        当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 

避免死锁:

  • 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  • 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

检测死锁

  1. 首先为每个进程和每个资源指定一个唯一的号码;
  2. 然后建立资源分配表和进程等待表。

解除死锁:

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  1. 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  2. 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。 

死锁检测

1、Jstack命令

        jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

2、JConsole工具

        Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。 

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

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

相关文章

【pen200-lab】10.11.1.217

pen200-lab 学习笔记 【pen200-lab】10.11.1.217 🔥系列专栏:pen200-lab 🎉欢迎关注🔎点赞👍收藏⭐️留言📝 📆首发时间:🌴2022年11月30日🌴 🍭作…

node.js的认识与安装

一、node.js的认识 📖 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个开源的、跨平台的JavaScript 运行时环境。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引…

ZMQ之脱机可靠性--巨人模式

当你意识到管家模式是一种非常可靠的消息代理时,你可能会想要使用磁盘做一下消息中转,从而进一步提升可靠性。这种方式虽然在很多企业级消息系统中应用,但我还是有些反对的,原因有: 1、我们可以看到,懒惰海…

【JS】数据结构之栈

文章目录基本介绍代码实现基本介绍 内存中的堆栈和数据机构中的堆栈不是一个概念,内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象数据存储结构。 栈:是一种受限制的线性表。他遵循后进先出的原则(LIFO)其限制…

神仙级编程神器,吹爆

Visual Studio 编程领域公认的“最强IDE”,Visual Studio是目前最流行的Windows平台应用程序的集成开发环境,提供了高级开发工具、调试功能、数据库功能和创新功能,帮助在各种平台上快速创建当前最先进的应用程序,开发新的程序。 …

【ODX介绍】-5-用于Flash刷写的ODX-F文件概述

总目录:(单击下方链接皆可跳转至专栏总目录) 《UDS/OBD诊断需求编辑工具》总目录https://blog.csdn.net/qfmzhu/article/details/123697014 共9页精讲:在第二章节中,附上了一个完整的,且详细的ODX-F文件层级结构图。 目录 1 什么是ODX-F?

【在Spring MVC框架和Spring Boot项目中,控制器的响应结果】

目录 1. 控制器的响应结果 2. 相关配置 3. 使用枚举优化代码 1. 控制器的响应结果 当控制器处理了请求之后,向客户端响应的结果中,应该至少包含: 业务状态码:通常是数值类型的,客户端可以根据此数值来判断操作成功…

docke部署nodejs程序及Dockerfile详解

目录参考一、Dockerfile二、部署1、程序结构2、新建Dockerfile3、新建.dockerignore4、构建镜像5、创建容器6、关闭镜像参考 重点参考:把一个 Node.js web 应用程序给 Docker 化 Docker部署Node.js的方法步骤(nodejs docker部署) 一、Docke…

Linux服务器启动tomcat的三种方式

直接进入主题,首先cd进入tomcat的bin文件夹下,然后可以尝试以下三种启动方式: 第一种(当前会话启动): ./startup.sh 效果: 然后tomcat就在后台启动了,我们还可以在当前会话中继续输入其它指令…

PHP基于thinkphp的网上书店管理系统#毕业设计

本论文主要论述了如何使用php语言开发一个网上图书管理系统,本系统将严格按照软件开发流程进行各个阶段的工作,面向对象编程思想进行项目开发。在引言中,将论述网上图书管理系统的当前背景以及系统开发的目的,后续章节将严格按照软…

【python】 16进制字符串转list

def splitStringToByteList(bytesString): # 拆分字符串成字节列表bytesList []for i in range(int(len(bytesString)/2)):bytesList.append(bytesString[i*2:i*22])return bytesListif __name__ __main__:print(splitStringToByteList("1E1E2AEB4ACC4C")) 结果&…

shiro key文件

​下面结合实战以及shiro的CookieRememberMeManaer的调用过程,浅谈获取shiro key文件的几种方式。 shiro key文件的获取方式:1结合Dnslog与URLDNS;2利用时间延迟或报错;3结合CookieRememberMeManaer 1结合Dnslog与URLDNS 在进行漏洞探测的时候,一般会使用ysoserial-URL…

Codeforces Round #835 (Div. 4)A.B.C.D.E.F

A. Medium Number 题目链接: Problem - A - Codeforces 题面: 题意: 给定三个数,求中间那个数的值 思路: 我们可以分别求出三个数的总和,最大值和最小值,在通过总和减最大值和最小值的方…

Promise(微任务)- 让你看完就懂

1. 图示 思维导图链接 https://www.zhixi.com/view/23ff2291 2. 使用promise原因 在没有promise的时候,一直使用setTimeout函数,这样就会造成回调地狱。 3. 基本状态 promise 有三种状态 pending(此时 promise还没有调用完成&#xff09…

改进花朵授粉算法的无线传感器网络部署优化(Matlab代码实现)

🍒🍒🍒欢迎关注🌈🌈🌈 📝个人主页:我爱Matlab 👍点赞➕评论➕收藏 养成习惯(一键三连)🌻🌻🌻 🍌希…

某汽车座椅进行的头冲吸能实验的仿真计算

本案例运用hypermesh和lsdyna联合仿真,主要是针对某座椅进行的头冲吸能实验的仿真计算。这个工况考察座椅背部的塑料件的破坏情况,以及头部模块的加速度情况,达到保护人头部的效果。 本案例用户可以学习到: 1、螺栓预紧力的施加…

IntelliJ IDEA + spring-boot+mysql简单实现获取数据库数据接口例子

一、新建一个spring-boot项目 demo 项目结构与文件的含义: 二、项目编译入口代码 DemoApplication.java SpringBootApplication //EnableAutoConfiguration(exclude{DataSourceAutoConfiguration.class}) public class DemoApplication {public static void main(…

Linux系统编程

Linux系统中所见皆文件 bin:所存放二进制可执行文件 boot:存放开机启动程序 dev:存放设备文件 home:存放用户 etc:用户信息和系统配置文件 lib:库文件 root:管理员宿主目录(家目录&…

Linux计划任务管理,网络管理

一,计划任务管理: 任务管理很宽泛,这里是指的计划任务管理,在指定的时间执行。 at命令 : 由atd守护进程来执行,atd进程会定期检查系统上的 /var/spool/at 目录,获取at命令写入的任务。 &#x…

Linux CentOS系统安装mysql8.0.31(tar方式)

Linux系统安装mysql8.0.31(TAR方式) 文章目录Linux系统安装mysql8.0.31(TAR方式)安装包的下载安装Mysql8.0.31注册成系统服务并自启动问题总结启动异常一简单使用安装包的下载 选择的是 MySQL Community Server 进行下载 官网下…