当前位置: 首页 > news >正文

什么是线程死锁?如何解决死锁问题

死锁,一组互相竞争的资源的线程之间相互等待,导致永久阻塞的现象。

如下图所示:
在这里插入图片描述
与死锁对应的,还有活锁,是指线程没有出现阻塞,但是无限循环。


有一个经典的银行转账例子如下:

我们有个账户类,其中有两个属性:账户名和余额。
它有两个方法:转入、转出。

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();}}

相关文章:

什么是线程死锁?如何解决死锁问题

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

C语言几种判断语句简述

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

【python学习笔记】:SQL常用脚本(二)

11、四舍五入ROUND函数 ROUND ( numeric_expression , length [ ,function ] ) function 必须为 tinyint、smallint 或 int。 如果省略 function 或其值为 0&#xff08;默认值&#xff09;&#xff0c;则将舍入 numeric_expression。 如果指定了0以外的值&#xff0c;则将截…...

【Linux】进程地址空间

文章目录&#x1f3aa; 进程地址空间&#x1f680;1.写时拷贝与虚拟地址&#x1f680;2.地址空间引入&#x1f680;3.地址空间的意义⭐3.1 虚拟地址寻址⭐3.2 虚拟地址意义&#x1f3aa; 进程地址空间 地址空间&#xff08;address space&#xff09;表示任何一个计算机实体所…...

Qt音视频开发17-vlc内核回调拿图片进行绘制

一、前言 在众多播放器中&#xff0c;支持的种类格式众多&#xff0c;并支持DVD影音光盘&#xff0c;VCD影音光盘及各类流式协议&#xff0c;提供了sdk进行开发&#xff0c;这点是至关重要的&#xff0c;尽管很多优秀的播放器很牛逼&#xff0c;由于没有提供sdk第三方开发&…...

安装配置DHCP

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

MarkDown中写UML图的方法

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

Axure8设计—动态仪表盘

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

【C++】类和对象的六个默认成员函数

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

4、算法MATLAB---认识矩阵

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

vue3+rust个人博客建站日记2-确定需求

反思 有人说过我们正在临近代码的终结点。很快&#xff0c;代码就会自动产生出来&#xff0c;不需要再人工编写。程序员完全没用了&#xff0c;因为商务人士可以从规约直接生成程序。 扯淡&#xff01;我们永远抛不掉代码&#xff0c;因为代码呈现了需求的细节。在某些层面上&a…...

Linux安装云原生网关Kong/KongA

目录1 概述2 创建服务器3 安装postgres4 安装kong5 安装node6 安装KONGA1 概述 Kong Kong是一款基于OpenResty&#xff08;NginxLua模块&#xff09;编写的高可用、易扩展的开源API网关&#xff0c;专为云原生和云混合架构而建&#xff0c;并针对微服务和分布式架构进行了特别…...

Vue学习笔记(2)

2.1 事件处理 2.1.1 事件监听器 JavaScript&#xff1a;通过获取DOM对象再往DOM对象上使用addEventListener注册监听事件 const btn document.querySelector(#my-button) btn.addEventListener(click, function() {alert(点击事件!) })jQuery&#xff1a;通过$选择器绑定对象…...

2023年三月份图形化四级打卡试题

活动时间 从2023年3月1日至3月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; 小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 小朋友做完题目后&#xff0c;截图到朋友圈打卡并把打卡的截图发到活动群…...

Python操作Excel

Python中对Excel文件的操作包括&#xff1a;读、写、修改。如果要对其进行如上的操作需要导入Python的第三方模块&#xff1a;xlrd、xlwd、xlutils&#xff0c;其分别对应Python的读、写、修改的操作 一、安装Python的第三方模块 二、操作Excel的基本步骤 1、导入响对应的模…...

Codeforces Round #853 (Div. 2) C. Serval and Toxel‘s Arrays【统计次数,算贡献】

链接 传送门 分析 这道题想法其实很简单&#xff0c;样例的计算方法一定要看懂。以样例1为例&#xff0c;根据他的操作方法可以得到两个新的数组&#xff0c;和一个原来的数组&#xff0c;总共三个数组。 1 2 3 4 2 3 4 5 3 他们两两配对去重&#xff0c;求出总的value。由于每…...

微信小程序-1:比较两数的大小

程序来源》微信小程序开发教程&#xff08;第二章&#xff09; 主编&#xff1a;黄寿孟、易芳、陶延涛 ISBN&#xff1a; 9787566720788 程序运行结果&#xff1a; <!--index.wxml--> <view class"container"> <text>第一个数字&#xff1a;&…...

数据结构——树

深度优先/广度优先遍历深度优先&#xff1a;访问根节点对根节点的 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 中&#xff0c;yield 是一个关键字&#xff0c;它用于定义生成器函数。生成器函数是一个特殊的函数&#xff0c;可以返回一个迭代器&#xff0c;当生成器函数被调用时&#xff0c;它不会立即执行&#xff0c;而是返回一个生成器对象&#xff0c;通过迭代生成器对象可…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...