什么是线程死锁?如何解决死锁问题
死锁,一组互相竞争的资源的线程之间相互等待,导致永久阻塞的现象。
如下图所示:
与死锁对应的,还有活锁,是指线程没有出现阻塞,但是无限循环。
有一个经典的银行转账例子如下:
我们有个账户类,其中有两个属性:账户名和余额。
它有两个方法:转入、转出。
public class Account {private String countName;private int balance;public Account(String countName, int balance) {this.countName = countName;this.balance = balance;}/*** 转出金额,更新转出方余额,金额减少* @param amount*/public void debit(int amount){this.balance -= amount;}/*** 存入金额,更新转入方余额,金额增多* @param amount*/public void credit(int amount){this.balance += amount;}public String getCountName() {return countName;}public void setCountName(String countName) {this.countName = countName;}public int getBalance() {return balance;}public void setBalance(int balance) {this.balance = balance;}
}
还有一个转账操作类,它有三个属性:转入账户、转出账户、转账金额。
它实现了Runnable接口,run方法中是转账逻辑代码。
我们使用 while(true) 让他不停的进行转账操作:如果账户余额大于转账金额,就让转出账号减少amount,转入账户增加amount。打印出线程名、金额转移方向、以及每个账户余额。
我们在main方法中进行测试,创建两个转账账户,使用两个线程操作这两个账户,让他们相互转账。
public class TransferAccount implements Runnable{private Account fromAccount;private Account toAccount;private int amount;public TransferAccount(Account fromAccount, Account toAccount, int amount) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;}@Overridepublic void run() {while(true){synchronized (fromAccount){synchronized (toAccount){if(fromAccount.getBalance()>=amount) {fromAccount.debit(amount);toAccount.credit(amount);}System.out.println(Thread.currentThread().getName());System.out.println(fromAccount.getCountName()+"->"+toAccount.getCountName()+":"+amount );System.out.println(fromAccount.getCountName() +"账户余额:"+ fromAccount.getBalance());System.out.println(toAccount.getCountName() +"账户余额:"+ toAccount.getBalance());}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Account bigHead = new Account("冤大头",100000);Account smallKill = new Account("门小抠",200000);new Thread(new TransferAccount(bigHead,smallKill,10)).start();new Thread(new TransferAccount(smallKill,bigHead,20)).start();}}
执行main方法后的输出结果:
Thread-0
冤大头->门小抠:10
冤大头账户余额:99990
门小抠账户余额:200010
Thread-1
门小抠->冤大头:20
门小抠账户余额:199990
冤大头账户余额:100010
Thread-1
门小抠->冤大头:20
门小抠账户余额:199970
冤大头账户余额:100030
Thread-0
冤大头->门小抠:10
冤大头账户余额:100020
门小抠账户余额:199980
Thread-0
冤大头->门小抠:10
冤大头账户余额:100010
门小抠账户余额:199990
Thread-1
门小抠->冤大头:20
门小抠账户余额:199970
冤大头账户余额:100030
Thread-0
冤大头->门小抠:10
冤大头账户余额:100020
门小抠账户余额:199980
Thread-1
门小抠->冤大头:20
门小抠账户余额:199960
冤大头账户余额:100040
Thread-0
冤大头->门小抠:10
冤大头账户余额:100030
门小抠账户余额:199970
Thread-1
门小抠->冤大头:20
门小抠账户余额:199950
冤大头账户余额:100050
Thread-0
冤大头->门小抠:10
冤大头账户余额:100040
门小抠账户余额:199960
Thread-1
门小抠->冤大头:20
门小抠账户余额:199940
冤大头账户余额:100060
这时,我们发现进程还在运行,但是控台停止输出了,它停在那里了。
截图如下:
这时我们使用 jps 来查看一下java进程:
D:\open_source\MyBatis\MyThread\target\classes\demo>jps
23600
24676 Launcher
40244 Jps
23672 TransferAccount
然后输入 使用jstack来看详情:
D:\open_source\MyBatis\MyThread\target\classes\demo>jstack 23672
2023-02-26 18:37:57
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.351-b10 mixed mode):"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001b4fc785800 nid=0x6530 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Thread-1" #13 prio=5 os_prio=0 tid=0x000001b4fc77f800 nid=0x887c waiting for monitor entry [0x000000467adff000]java.lang.Thread.State: BLOCKED (on object monitor)at deadlock.TransferAccount.run(TransferAccount.java:20)- waiting to lock <0x000000076c5a4610> (a deadlock.Account)- locked <0x000000076c5a4658> (a deadlock.Account)at java.lang.Thread.run(Thread.java:750)"Thread-0" #12 prio=5 os_prio=0 tid=0x000001b4fc77c800 nid=0x8c80 waiting for monitor entry [0x000000467acff000]java.lang.Thread.State: BLOCKED (on object monitor)at deadlock.TransferAccount.run(TransferAccount.java:20)- waiting to lock <0x000000076c5a4658> (a deadlock.Account)- locked <0x000000076c5a4610> (a deadlock.Account)at java.lang.Thread.run(Thread.java:750)......Found one Java-level deadlock:
=============================
"Thread-1":waiting to lock monitor 0x000001b4fa208eb8 (object 0x000000076c5a4610, a deadlock.Account),which is held by "Thread-0"
"Thread-0":waiting to lock monitor 0x000001b4fa208e08 (object 0x000000076c5a4658, a deadlock.Account),which is held by "Thread-1"Java stack information for the threads listed above:
===================================================
"Thread-1":at deadlock.TransferAccount.run(TransferAccount.java:20)- waiting to lock <0x000000076c5a4610> (a deadlock.Account)- locked <0x000000076c5a4658> (a deadlock.Account)at java.lang.Thread.run(Thread.java:750)
"Thread-0":at deadlock.TransferAccount.run(TransferAccount.java:20)- waiting to lock <0x000000076c5a4658> (a deadlock.Account)- locked <0x000000076c5a4610> (a deadlock.Account)at java.lang.Thread.run(Thread.java:750)Found 1 deadlock.
我们可以看到,其中Thread-0和Thread-1都已经处于BLOCK状态,发生了死锁。
导致死锁发生,必须同时满足四个条件:互斥、占有且等待、不可抢占、循环等待。
- 互斥:共享资源A和B只能被一个线程占用。
- 占有且等待:线程Thread-1 已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X。
- 不可抢占:其他线程不能强行抢占线程Thread-1 占有的资源。
- 循环等待:线程Thread-1 等待线程Thread-2 占有的资源,线程Thread-2等待Thread-1 占有的资源。
如何解决?
如果要解决死锁问题,只需要破坏其中一个条件,使其不满足即可。
除了互斥(多线程的基础)之外,其他三个条件都可以考虑破坏。
实际中遇到死锁,只能重启,如果还是死锁,要定位问题点,然后修复代码(破坏掉死锁必须满足的条件之一)后重新发布。
我们来尝试破坏占有且等待的方式来破坏死锁:
新增加一个分配的类:Allocator,它有一个账户池。每个转账线程中要判断,账户池中是否已有此账户,如果没有可以转账,有的话就不转账。就可以避免共享资源同时存在于两个线程。
import java.util.ArrayList;
import java.util.List;public class Allocator {private List<Object> list = new ArrayList<>();synchronized boolean apply(Object fromAccount,Object toAccount){if(list.contains(fromAccount)||list.contains(toAccount)){return false;}list.add(fromAccount);list.add(toAccount);return true;}synchronized void free(Object fromAccount,Object toAccount){list.remove(fromAccount);list.remove(toAccount);}
}//相应的,TransAcount也要做修改:
public class TransferAccount implements Runnable {private Account fromAccount;private Account toAccount;private int amount;private Allocator allocator;public TransferAccount(Account fromAccount, Account toAccount, int amount, Allocator allocator) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;this.allocator = allocator;}@Overridepublic void run() {while (true) {//判断账户是否已经分配过if (allocator.apply(fromAccount, toAccount)) {try {synchronized (fromAccount) {synchronized (toAccount) {if (fromAccount.getBalance() >= amount) {fromAccount.debit(amount);toAccount.credit(amount);}System.out.println(Thread.currentThread().getName());System.out.println(fromAccount.getCountName() + "->" + toAccount.getCountName() + ":" + amount);System.out.println(fromAccount.getCountName() + "账户余额:" + fromAccount.getBalance());System.out.println(toAccount.getCountName() + "账户余额:" + toAccount.getBalance());}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}finally {allocator.free(fromAccount,toAccount);}}}}public static void main(String[] args) {Account bigHead = new Account("冤大头", 100000);Account smallKill = new Account("门小抠", 200000);Allocator allocator = new Allocator();new Thread(new TransferAccount(bigHead, smallKill, 10, allocator)).start();new Thread(new TransferAccount(smallKill, bigHead, 20, allocator)).start();}}
我们来避免第三个条件:使用Lock来替换掉 Synchronized。
Synchronized加锁后,要等到资源释放,而且锁是不可抢占的。
Lock中有个方法 tryLock,可以返回布尔值,如果返回fasle就不会进去。tryLock不会持续持有锁。
public class TransferAccount2 implements Runnable{private Account fromAccount;private Account toAccount;private int amount;private Lock fromAccountLock = new ReentrantLock();private Lock toAccountLock = new ReentrantLock();public TransferAccount2(Account fromAccount, Account toAccount, int amount) {this.fromAccount = fromAccount;this.toAccount = toAccount;this.amount = amount;}@Overridepublic void run() {while(true){//synchronized (fromAccount){if(fromAccountLock.tryLock()){if(toAccountLock.tryLock()){if(fromAccount.getBalance()>=amount) {fromAccount.debit(amount);toAccount.credit(amount);}System.out.println(Thread.currentThread().getName());System.out.println(fromAccount.getCountName()+"->"+toAccount.getCountName()+":"+amount );System.out.println(fromAccount.getCountName() +"账户余额:"+ fromAccount.getBalance());System.out.println(toAccount.getCountName() +"账户余额:"+ toAccount.getBalance());}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Account bigHead = new Account("冤大头",100000);Account smallKill = new Account("门小抠",200000);new Thread(new TransferAccount2(bigHead,smallKill,10)).start();new Thread(new TransferAccount2(smallKill,bigHead,20)).start();}}
相关文章:

什么是线程死锁?如何解决死锁问题
死锁,一组互相竞争的资源的线程之间相互等待,导致永久阻塞的现象。 如下图所示: 与死锁对应的,还有活锁,是指线程没有出现阻塞,但是无限循环。 有一个经典的银行转账例子如下: 我们有个账户类…...

C语言几种判断语句简述
C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。 C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 fals…...

【python学习笔记】:SQL常用脚本(二)
11、四舍五入ROUND函数 ROUND ( numeric_expression , length [ ,function ] ) function 必须为 tinyint、smallint 或 int。 如果省略 function 或其值为 0(默认值),则将舍入 numeric_expression。 如果指定了0以外的值,则将截…...

【Linux】进程地址空间
文章目录🎪 进程地址空间🚀1.写时拷贝与虚拟地址🚀2.地址空间引入🚀3.地址空间的意义⭐3.1 虚拟地址寻址⭐3.2 虚拟地址意义🎪 进程地址空间 地址空间(address space)表示任何一个计算机实体所…...

Qt音视频开发17-vlc内核回调拿图片进行绘制
一、前言 在众多播放器中,支持的种类格式众多,并支持DVD影音光盘,VCD影音光盘及各类流式协议,提供了sdk进行开发,这点是至关重要的,尽管很多优秀的播放器很牛逼,由于没有提供sdk第三方开发&…...

安装配置DHCP
本次实验采用CentOS71.检查在安装DHCP之前先使用rpm命令查看系统中已有的DHCP软件包rpm -qa | grep dhcp由此可知,系统中尚未安装DHCP软件包2.安装我们可以使用yum命令为系统安装DHCP软件包yum -y install dhcp安装完成后再次检查可以看到DHCP软件包3.配置dhcp配置文…...

MarkDown中写UML图的方法
目录序UML图之顺序图顺序图的四个要素关于消息箭头的语法Mermaid中顺序图的简单例子样例用小人表示对象为对象设置别名激活对象UML图之类图类图中常见的关系关于不同类型关系的语法Mermaid中类图的简单例子样例类定义的两种方式为类定义成员双向关系的表示多重性关系的表示UML之…...

Axure8设计—动态仪表盘
本次分享的的案例是Axure8制作的动态仪表盘,根据设置的数值,仪表盘指针旋转到相应的值位置 预览地址:https://2qiuwg.axshare.com 下载地址:https://download.csdn.net/download/weixin_43516258/87502161 一、制作原型 1、首先创建空白页…...

【C++】类和对象的六个默认成员函数
类的6个默认成员函数构造函数概念特性析构函数概念特性拷贝构造函数概念特征拷贝构造函数典型调用场景:赋值运算符重载运算符重载赋值运算符重载取地址及const取地址操作符重载类的6个默认成员函数 到底什么是类的6个默认成员函数呢?相信大家一定对此怀…...

4、算法MATLAB---认识矩阵
认识矩阵1、矩阵定义和基本运算1.1 赋值运算符:1.2 等号运算符:1.3 空矩阵1.4 一行一列矩阵1.5 行矩阵(元素用空格或逗号分隔)1.6 列矩阵(分号表示换行)1.7 m行n列的矩阵:行值用逗号间隔&#x…...

vue3+rust个人博客建站日记2-确定需求
反思 有人说过我们正在临近代码的终结点。很快,代码就会自动产生出来,不需要再人工编写。程序员完全没用了,因为商务人士可以从规约直接生成程序。 扯淡!我们永远抛不掉代码,因为代码呈现了需求的细节。在某些层面上&a…...

Linux安装云原生网关Kong/KongA
目录1 概述2 创建服务器3 安装postgres4 安装kong5 安装node6 安装KONGA1 概述 Kong Kong是一款基于OpenResty(NginxLua模块)编写的高可用、易扩展的开源API网关,专为云原生和云混合架构而建,并针对微服务和分布式架构进行了特别…...
Vue学习笔记(2)
2.1 事件处理 2.1.1 事件监听器 JavaScript:通过获取DOM对象再往DOM对象上使用addEventListener注册监听事件 const btn document.querySelector(#my-button) btn.addEventListener(click, function() {alert(点击事件!) })jQuery:通过$选择器绑定对象…...

2023年三月份图形化四级打卡试题
活动时间 从2023年3月1日至3月21日,每天一道编程题。 本次打卡的规则如下: 小朋友每天利用10~15分钟做一道编程题,遇到问题就来群内讨论,我来给大家答疑。 小朋友做完题目后,截图到朋友圈打卡并把打卡的截图发到活动群…...
Python操作Excel
Python中对Excel文件的操作包括:读、写、修改。如果要对其进行如上的操作需要导入Python的第三方模块:xlrd、xlwd、xlutils,其分别对应Python的读、写、修改的操作 一、安装Python的第三方模块 二、操作Excel的基本步骤 1、导入响对应的模…...
Codeforces Round #853 (Div. 2) C. Serval and Toxel‘s Arrays【统计次数,算贡献】
链接 传送门 分析 这道题想法其实很简单,样例的计算方法一定要看懂。以样例1为例,根据他的操作方法可以得到两个新的数组,和一个原来的数组,总共三个数组。 1 2 3 4 2 3 4 5 3 他们两两配对去重,求出总的value。由于每…...

微信小程序-1:比较两数的大小
程序来源》微信小程序开发教程(第二章) 主编:黄寿孟、易芳、陶延涛 ISBN: 9787566720788 程序运行结果: <!--index.wxml--> <view class"container"> <text>第一个数字:&…...

数据结构——树
深度优先/广度优先遍历深度优先:访问根节点对根节点的 children 挨个进行深度优先遍历const tree {val: "a",children: [{val: "b",children: [{val: "d",children: [],},{val: "e",children: [],},],},{val: "c&quo…...

【华为OD机试模拟题】用 C++ 实现 - 找到它(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明找到它题目输入输出示例一输入输出示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD …...
python中yield的使用
在 Python 中,yield 是一个关键字,它用于定义生成器函数。生成器函数是一个特殊的函数,可以返回一个迭代器,当生成器函数被调用时,它不会立即执行,而是返回一个生成器对象,通过迭代生成器对象可…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...

GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...

Tauri2学习笔记
教程地址:https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引:https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多,我按照Tauri1的教程来学习&…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)
注:文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件:STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...