大白话拆解——多线程中关于死锁的一切(七)(已完结)

前言:

25年初,这个时候好多小伙伴都在备战期末

小编明天还有一科考试,日更一篇,今天这篇一定会对小白非常有用的!!!

因为我们会把案例到用代码实现的全过程思路呈现出来!!!

我们一直都是以这样的形式,让新手小白轻松理解复杂晦涩的概念,把Java代码拆解的清清楚楚,每一步都知道他是怎么来的,为什么用这串代码关键字,对比同类型的代码,让大家真正看完以后融会贯通,举一反三,实践应用!!!!



①官方定义  和  大白话拆解对比

②举生活中常见贴合例子、图解辅助理解的形式

③对代码实例中关键部分进行详细拆解、总结


我们今天就不回顾上篇的内容了,直接继续

6.2 死锁

官方语言:

  • 死锁(Deadlock)是指在多线程或多进程环境中,两个或多个线程或进程因互相持有对方需要的资源而不放弃,并且都在等待对方释放资源的一种阻塞现象。
  • 在这种情况下,没有一个线程或进程能够继续执行,因为它们都卡在等待状态,导致整个系统或程序的部分功能停滞不前。
  • 死锁是并发编程中常见的问题之一,通常与同步机制如锁(Lock)、互斥量(Mutex)、信号量(Semaphore)等有关。

死锁发生的必要条件包括:

  • 互斥条件:至少有一个资源必须以非共享模式存在,即一次只能被一个线程占有。
  • 占有并等待条件:一个线程必须占用至少一个资源,并等待获取当前被其他线程占用的额外资源。
  • 不可剥夺条件:资源不能被强制从占用它的线程那里夺走;资源只能由占用它的线程主动释放。
  • 循环等待条件:存在一个线程的循环链,每个线程都拥有下一个线程所需要的资源。


大白话拆解:

  • 想象一下你和你的朋友正在玩交换礼物的游戏,但你们俩同时拿起了对方想要的礼物,然后都不愿意先放下自己的礼物去拿对方的。这样你们就陷入了僵局,谁也无法完成交换礼物的动作,因为大家都在等着对方先行动。这种情况在计算机科学里被称为“死锁”。
  • 你和你的室友都有各自的电脑,但是现在你需要用他的电脑上的某个软件,他需要你电脑上的另一个软件。你们俩决定互换电脑使用,但当你们走到彼此面前的时候,发现对方正坐在自己的电脑前,等着你先让出位置。于是你们俩就在那儿坐着,互相等待对方先起身,结果就是谁也没能用上对方的电脑,形成了“死锁”。

举个栗子:

银行转账系统的场景,其中有两个账户Account A和Account B,以及两个线程Thread 1和Thread 2。

  • 这两个线程负责处理不同用户发起的转账请求。假设Thread 1要将钱从Account A转到Account B,而Thread 2要将钱从Account B转到Account A。如果两个线程几乎同时开始操作,可能会发生以下情况:
  • Thread 1获得了Account A的锁,准备从中取款。
  • Thread 2获得了Account B的锁,准备从中取款。
  • 接下来,Thread 1尝试获得Account B的锁以便向其存款,但是因为Thread 2已经持有了这个锁,所以Thread 1进入等待状态。
  • 同时,Thread 2也尝试获得Account A的锁以便向其存款,但是因为Thread 1已经持有了这个锁,所以Thread 2也进入等待状态。
  • 此时,Thread 1在等待Thread 2释放Account B的锁,而Thread 2在等待Thread 1释放Account A的锁,两者都不会主动放弃自己持有的锁,从而形成死锁。
public class DeadlockExample {// 定义两个账户static Account accountA = new Account("Account A");static Account accountB = new Account("Account B");public static void main(String[] args) throws InterruptedException {// 创建两个线程,每个线程负责一个转账操作Thread thread1 = new Thread(new TransferTask(accountA, accountB, 100), "Thread 1");Thread thread2 = new Thread(new TransferTask(accountB, accountA, 50), "Thread 2");// 启动线程thread1.start();thread2.start();// 等待线程结束thread1.join();thread2.join();}
}class Account {private String name;private int balance;public Account(String name) {this.name = name;this.balance = 1000; // 初始余额}public synchronized void withdraw(int amount) {if (amount > balance) {System.out.println(Thread.currentThread().getName() + ": Insufficient funds in " + name);return;}balance -= amount;System.out.println(Thread.currentThread().getName() + ": Withdrew " + amount + " from " + name);}public synchronized void deposit(int amount) {balance += amount;System.out.println(Thread.currentThread().getName() + ": Deposited " + amount + " into " + name);}@Overridepublic String toString() {return "Account{" +"name='" + name + '\'' +", balance=" + balance +'}';}
}class TransferTask implements Runnable {private final Account fromAccount;private final Account toAccount;private final int amount;public TransferTask(Account fromAccount, Account toAccount, int amount) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;}@Overridepublic void run() {// 尝试获取两个账户的锁synchronized (fromAccount) {System.out.println(Thread.currentThread().getName() + ": Locked " + fromAccount.getName());fromAccount.withdraw(amount); // 从来源账户取款try {// 模拟处理延迟Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (toAccount) { // 尝试锁定目标账户System.out.println(Thread.currentThread().getName() + ": Locked " + toAccount.getName());toAccount.deposit(amount); // 向目标账户存款}}}
}

代码解释和总结

  • Account 类:这个类代表一个银行账户。它有两个方法,withdraw 用来取钱,deposit 用来存钱。这两个方法前都有 synchronized 关键字,这意味着如果一个线程正在使用 withdraw 或 deposit 方法,那么其他线程必须等待,直到该线程完成操作并释放了账户的锁。
  • TransferTask 类:这个类代表一个转账任务。它实现了 Runnable 接口,所以它可以被一个线程执行。它的 run 方法定义了当线程开始运行时要做的工作。在这个例子中,它会先锁定来源账户,然后尝试锁定目标账户。如果另一个线程已经锁定了目标账户,当前线程就会等待,直到目标账户的锁被释放。
  • DeadlockExample 类:这是主类,包含 main 方法,是程序的入口点。这里我们创建了两个账户和两个线程。每个线程都试图执行一个转账操作,但是由于它们几乎同时启动,并且按照不同的顺序尝试锁定两个账户,就有可能形成死锁。
  • 同步块:在 TransferTask 的 run 方法中,我们使用了 synchronized 块来确保在同一时间只有一个线程可以访问特定的账户。这就像是一把锁,一次只能有一个人进入房间。
  • 模拟处理延迟:Thread.sleep(100); 这一行是用来模拟实际转账过程中可能会有的处理时间。在这段时间里,线程保持持有账户的锁,增加了死锁的可能性。

死锁发生的原因

  • 在这个例子中,死锁可能发生是因为两个线程以相反的顺序尝试获取相同的资源(即两个账户的锁)。具体来说:
  • Thread 1 先锁住了 Account A,然后尝试去锁住 Account B。
  • Thread 2 先锁住了 Account B,然后尝试去锁住 Account A。
  • 如果两个线程恰好在对方尝试获取自己持有的锁之前成功获取了第一个锁,那么它们都会卡在那里,等待对方释放锁,而对方也在等自己释放锁,结果就是谁都无法继续前进,形成了死锁。

诱发死锁的原因:

我们还是以上面的代码为例,

上面例子中:TransferTask类实现了转账操作,它在执行时会尝试获取两个账户对象的锁。由于两个线程分别试图以相反的顺序锁定相同的两个账户(accountA和accountB),这就创建了一个潜在的死锁场景。具体来说,死锁可能发生的原因如下:

  • 互斥条件:每个账户上的synchronized方法确保在同一时间只有一个线程可以访问该账户。这是产生死锁的第一个必要条件。
  • 持有并等待资源:当一个线程已经持有一个锁(比如fromAccount),然后去尝试获取另一个锁(toAccount)时,如果此时另一个线程也正在持有第二个锁而尝试获取第一个锁,就会出现这种情况。这满足了死锁的第二个条件。
  • 非抢占条件:Java的锁是不可抢占的,即一旦一个线程获得了锁,它就不能被强制释放,只有当线程自己释放锁的时候才能释放。这满足了死锁的第三个条件。
  • 循环等待条件:这里存在一个潜在的循环等待链,例如:
  • Thread 1 持有 accountA 的锁,并等待 accountB 的锁。
  • Thread 2 持有 accountB 的锁,并等待 accountA 的锁。 这样就形成了一个循环等待链,满足了死锁的第四个条件。

解决死锁:

策略1:一次性申请所有所需的资源


大白话拆解:

  • 你和你的朋友打算一起做饭,你们需要不同的厨具。为了避免两个人同时拿起同一个厨具而卡住,你们可以事先商量好,每个人一次性拿走自己需要的所有厨具。这样,当一个人开始做饭时,他已经有了所有需要的东西,不需要再等其他人腾出厨具了。

应用到代码:

  • 在转账操作开始之前,我们可以尝试一次性获取两个账户的锁,而不是先获取一个账户的锁,然后再获取另一个。这可以通过尝试以相同的顺序锁定两个账户来实现,确保不会发生交叉锁定的情况。
class TransferTask implements Runnable {private final Account fromAccount;private final Account toAccount;private final int amount;public TransferTask(Account fromAccount, Account toAccount, int amount) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;}@Overridepublic void run() {// 以固定的顺序锁定账户,避免交叉锁定Object firstLock, secondLock;if (fromAccount.hashCode() < toAccount.hashCode()) {firstLock = fromAccount;secondLock = toAccount;} else {firstLock = toAccount;secondLock = fromAccount;}synchronized (firstLock) {System.out.println(Thread.currentThread().getName() + ": Locked " + ((Account) firstLock).getName());synchronized (secondLock) {System.out.println(Thread.currentThread().getName() + ": Locked " + ((Account) secondLock).getName());fromAccount.withdraw(amount);toAccount.deposit(amount);}}}
}

策略2:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源


大白话拆解:

  • 还是做饭的例子,如果你发现自己没有拿到全部需要的厨具,你可以选择把已经拿到的厨具放回去,让别人先使用,等他们用完后再重新尝试获取所有你需要的厨具。

应用到代码:

  • 我们可以尝试获取第二个锁,如果获取失败,那么我们就释放第一个锁,然后可能重试或者退出操作。Java 中的 tryLock 方法可以帮助我们实现这一点。
import java.util.concurrent.locks.ReentrantLock;class TransferTask implements Runnable {private final ReentrantLock lockA = new ReentrantLock();private final ReentrantLock lockB = new ReentrantLock();private final Account fromAccount;private final Account toAccount;private final int amount;public TransferTask(Account fromAccount, Account toAccount, int amount) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;}@Overridepublic void run() {boolean hasLockA = false;boolean hasLockB = false;try {hasLockA = lockA.tryLock(500, TimeUnit.MILLISECONDS);if (!hasLockA) {System.out.println(Thread.currentThread().getName() + ": Could not acquire lock on " + fromAccount.getName());return;}hasLockB = lockB.tryLock(500, TimeUnit.MILLISECONDS);if (!hasLockB) {System.out.println(Thread.currentThread().getName() + ": Could not acquire lock on " + toAccount.getName());return;}// 成功获取两个锁后进行转账fromAccount.withdraw(amount);toAccount.deposit(amount);} catch (InterruptedException e) {e.printStackTrace();} finally {if (hasLockA) lockA.unlock();if (hasLockB) lockB.unlock();}}
}

策略3:将资源改为线性顺序


大白话拆解:

  • 如果我们有一个规则,比如说所有人都按照从左到右的顺序去拿厨具,就不会出现两个人互相等待对方放开手的情况了。

应用到代码:

  • 我们可以根据账户对象的哈希码(或任何其他唯一标识符)来决定哪个账户应该首先被锁定。这样可以保证所有线程总是以相同的顺序锁定账户,从而避免循环等待。
  • 上面的策略1已经展示了这种做法,我们就不用具体代码了再


我们今天就先到这里

下篇再见吧!!!

看在小编日更的份儿上了,点个关注好不好,我们一起进步!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

家教老师预约平台小程序系统开发方案

家教老师预约平台小程序系统将连接学生/家长与家教老师&#xff0c;提供一站式的家教服务预约体验。 一、用户需求分析1、家教老师&#xff1a;希望获得更多的学生资源&#xff0c;通过平台展示自己的教学特长和经验&#xff0c;管理个人日程&#xff0c;接收并确认预约请求&a…

windows 图形基础架构简介

背景 本文尝试对Windows系统中的一些Graphic相关的概念进行介绍和厘清。 windows图形基础架构简介 Windows 为图形提供了多个 API&#xff0c;下图显示了这些 API。 上图出自微软官方https://learn.microsoft.com/en-us/windows/win32/learnwin32/overview-of-the-windows-…

QML使用Popup实现弹出Message

方案一&#xff1a;popup import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15ApplicationWindow {visible: truewidth: 640height: 480title: qsTr("Top Message Popup Example")ColumnLayout {anchors.centerIn: parentspacing: 10Butt…

Ⅱ.INTRODUCTION TO CUDA C

前言 上一节环境配置好了&#xff0c;我们开始吧&#xff01; 一、A First Program 1. Hello, World! 我们先写一个C语言的 Hello, World! 作为对比 int main(void){printf("Hello, World!\n");return 0; }大家应该知道这个代码运行在CPU上吧&#xff0c;我们CP…

如何轻松关闭 iPhone 上的 HEIC [HEIC 图像技巧]

您是否正在为关闭 iPhone 上的 HEIC 而烦恼&#xff1f;你不是一个人; Apple 的首选图像文件格式仍可能存在一些兼容性问题。当您与某人共享照片或尝试在Windows计算机上打开图像时&#xff0c;就会出现此问题。幸运的是&#xff0c;Apple 使关闭 HEIC iPhone 变得更加容易。 …

Postgresql 命令还原数据库

因为PgAdmin打不开&#xff0c;但是数据库已经安装成功了&#xff0c;这里借助Pg命令来还原数据库 C:\Program Files\PostgreSQL\15\bin\psql.exe #链接数据库 psql -U postgres -p 5432#创建数据库 CREATE DATABASE "数据库名称"WITHOWNER postgresENCODING UTF8…

docker中使用Volume完成数据共享

情景概述 在一个docker中&#xff0c;部署两个MySQL容器&#xff0c;假如它们的数据都存储在自己容器内部的data目录中。这样的存储方式会有以下问题&#xff1a; 1.无法保证两个MySQL容器中的数据同步。 2.容器删除后&#xff0c;数据就会丢失。 基于以上问题&#xff0c;容…

vue——滑块验证

1. 介绍 1.1 简介 基于滑动式的验证码&#xff0c;免于字母验证码的繁琐输入 用于网页注册或者登录 1.2 来源说明 vue使用滑块验证功能&#xff0c;是基于vue-monoplasty-slide-verify这样的一个开源项目&#xff0c;进行实现的&#xff0c;这是这个开源项目的网址传送阵&#…

如何很快将文件转换成另外一种编码格式?编码?按指定编码格式编译?如何检测文件编码格式?Java .class文件编码和JVM运行期内存编码?

如何很快将文件转换成另外一种编码格式? 利用VS Code右下角的"选择编码"功能&#xff0c;选择"通过编码保存"可以很方便将文件转换成另外一种编码格式。尤其&#xff0c;在测试w/ BOM或w/o BOM, 或者ANSI编码和UTF编码转换&#xff0c;特别方便。VS文件另…

Unity3D仿星露谷物语开发16之角色拾取道具

1、目标 当角色经过道具时会拾取道具放到库存列表中&#xff0c;此时道具消失并打印库存信息。 2、创建新的Enum 在Assets -> Scripts -> Enums -> Enum.cs中添加库存位置相关的信息。 public enum InventoryLocation {player, // 在角色手中chest, // 在箱子里co…

UE4_用户控件_3_用户控件输入数据的方法

祝愿大美兰陵越来越好&#xff01; 一、效果展示&#xff1a; 二、先制作一个角色 1、新建个父类为pawn的蓝图类。更名为BP_Image_Character。 2、这个角色只是用于观察场景&#xff0c;并与场景中的物体相碰撞用的&#xff0c;所以不需要骨骼网格体&#xff0c; 3、但是我们…

文献阅读 | B. S. Carmo 2010

目录 一、文献名称二、原文地址三、ABSTRACT主要发现详细观察分岔分析雷诺数依赖性比较见解意义结论 四、IINTRODUCTION历史研究回顾计算研究近期研究进展研究空白与目的论文结构 一、文献名称 二、原文地址 研究目的&#xff1a;研究串列排列双圆柱体周围流场中的次级不稳定性…

vue3 css实现文字输出带光标显示,文字输出完毕,光标消失的效果

Vue实现过程如下&#xff1a; <template><div ><p ref"dom_element" class"typing" :class"{over_fill: record_input_over}"></p></div> </template> <script setup> import {onMounted, ref} from…

如何安装适配pytorch版本的torchvision

一、对照版本 版本对照pytorch/vision: Datasets, Transforms and Models specific to Computer Vision 二、下载对应版本的torchvision 下载连接1download.pytorch.org/whl/torch_stable.html 下载连接2download.pytorch.org/whl/cu110/torch_stable.html 笔者认为1会比2更…

Leetcode打卡:我的日程安排表III

执行结果&#xff1a;通过 题目 732 我的日程安排表 III 当 k 个日程存在一些非空交集时&#xff08;即, k 个日程包含了一些相同时间&#xff09;&#xff0c;就会产生 k 次预订。 给你一些日程安排 [startTime, endTime) &#xff0c;请你在每个日程安排添加后&#xff0c;…

TI毫米波雷达原始数据解析之Lane数据交换

TI毫米波雷达原始数据解析之Lane数据交换 背景Lane 定义Lane 确认确认LVDS Lane 数量的Matlab 代码数据格式参考 背景 解析使用mmWave Studio 抓取的ADC Data Lane 定义 芯片与DCA100之间的数据使用LVDS接口传输&#xff0c;使用mmWave Studio 配置过程中有一个选项是LVDS L…

redis7基础篇3 redis的集群模式3

一 集群模式 1.1 redis的集群模式 Redis集群模式&#xff0c;实现数据集在多个节点进行共享&#xff0c;支持多个master节点。 Redis集群支持多个master&#xff0c;每个master节点又可以挂载多个slave&#xff1b;由于cluster自带sentinel的故障转移机制&#xff0c;内置高…

【嵌入式硬件】直流电机驱动相关

项目场景&#xff1a; 驱动履带车&#xff08;双直流电机&#xff09;前进、后退、转弯 问题描述 电机驱动MOS管烧毁 电机驱动采用IR2104STRH1R403NL的H桥方案&#xff08;这是修改之后的图&#xff09; 原因分析&#xff1a; 1.主要原因是4路PWM没有限幅&#xff0c;修改…

部署项目添加工程名的步骤

1.首先要在router下进行工程名添加 2.其次在vue.config.js中添加publicpath 3.在nginx配置文件中 location /my-app/ {try_files $uri $uri/ /my-app/index.html; }

SCAU期末笔记 - 数据库系统概念往年试卷解析

数据库搞得人一头雾水&#xff0c;题型太多太杂&#xff0c;已经准备摆烂了。就刷刷往年试卷&#xff0c;挂不挂听天由命。 2019年 Question 1 选择题 1. R ∩ S R∩S R∩S等于一下哪个选项&#xff1f; 画个文氏图秒了 所以选A. R ∩ S R − ( R − S ) R∩SR-(R-S) R∩…