多线程——死锁
死锁
在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。
我们只能通过中止并重启的方式来让程序重新执行。
这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!
死锁的原因:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
锁顺序死锁
线程1调用
leftRight()方法,得到left锁
同时线程2调用rightLeft()方法,得到right锁线程1和线程2都继续执行,此时线程1需要right锁才能继续往下执行。此时线程2需要left锁才能继续往下执行。
线程1的left锁并没有释放,线程2的right锁也没有释放。
所以他们都只能等待,而这种等待是无期限的–>永久等待–>死锁
class Demo implements Runnable{private final Object left = new Object(); private final Object right = new Object();@Overridepublic void run() {leftRight();rightLeft();}public void leftRight() {synchronized (left) {System.out.println("线程" + Thread.currentThread().getName() + "拿到left,还需要right");synchronized (right) {System.out.println("线程" + Thread.currentThread().getName() + "拿到left和right");}}}public void rightLeft() {synchronized (right) {System.out.println("线程" + Thread.currentThread().getName() + "拿到right,还需要left");synchronized (left) {System.out.println("线程" + Thread.currentThread().getName() + "拿到right和left");}}}
}
动态锁顺序死锁
代码释义:获取源账户与目标账户,判断余额充足后进行加减操作。
死锁现象:线程1源账户a,目标账户b。线程2源账户b,目标账户a。
public class ThreadTest {public static void main(String[] args) {Test test = new Test();new Thread(test, "1").start();new Thread(test, "2").start();new Thread(test, "3").start();new Thread(test, "4").start();}
}class Test implements Runnable {Account a = new Account("A", 1000);Account b = new Account("B", 1000);@Overridepublic void run() {transferMoney(a, b, 100);transferMoney(b, a, 100);}public void transferMoney(Account fromAccount, Account toAccount, double money) {synchronized (fromAccount) {System.out.println("线程" + Thread.currentThread().getName() + "获得账户" + fromAccount.getName());synchronized (toAccount) {System.out.println("线程" + Thread.currentThread().getName() + "获得账户" + toAccount.getName());if (fromAccount.getMoney() < money) {System.out.println("余额不足");} else {fromAccount.setMoney(fromAccount.getMoney() - money);toAccount.setMoney(toAccount.getMoney() + money);System.out.println("转账后:" + fromAccount.getName() + "的余额:" +fromAccount.getMoney());System.out.println("转账后:" + toAccount.getName() + "的余额:" + toAccount.getMoney());}}}}
}
@Data
class Account {public Account(String name, double money) {this.name = name;this.money = money;}private String name;private double money;
}
协作对象之间发生死锁
getImage()和setLocation(Point location)都需要获取两个锁,且在操作途中是没有释放锁的。
这就是隐式获取两个锁(对象之间协作)很容易造成死锁
public class CooperatingDeadlock {class Taxi {private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}// setLocation 需要Taxi内置锁public synchronized void setLocation(Point location) {this.location = location;if (location.equals(destination))// 调用notifyAvailable()需要Dispatcher内置锁dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}class Dispatcher {private final Set<Taxi> taxis;private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}// 调用getImage()需要Dispatcher内置锁public synchronized Image getImage() {Image image = new Image();for (Taxi t : availableTaxis)// 调用getLocation()需要Taxi内置锁image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}
}
避免死锁的方法
避免死锁可以概括成三种方法:
- 固定加锁的顺序(针对锁顺序死锁)
- 开放调用(针对对象之间协作造成的死锁)
- 使用定时锁–>
tryLock()如果等待获取锁时间超时,则抛出异常而不是一直等待
固定锁顺序避免死锁
得到对应的hash值来固定加锁的顺序,这样不会出现死锁。
public class OrderLock {private static final Object tieLock = new Object();public void transferMoney(final Account fromAccount, final Account toAccount, final DollarAmount amount)throws InsufficientFundsException {class Helper {public void transfer() throws InsufficientFundsException {if (fromAccount.getBalance().compareTo(amount) < 0)throw new InsufficientFundsException();else {fromAccount.debit(amount);toAccount.credit(amount);}}}int fromHash = System.identityHashCode(fromAccount);int toHash = System.identityHashCode(toAccount);if (fromHash < toHash) {synchronized (fromAccount) {synchronized (toAccount) {new Helper().transfer();}}} else if (fromHash > toHash) {synchronized (toAccount) {synchronized (fromAccount) {new Helper().transfer();}}} else {synchronized (tieLock) {synchronized (fromAccount) {synchronized (toAccount) {new Helper().transfer();}}}}}
}
开放调用避免死锁
在协作对象之间发生死锁的例子中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法。
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。同步代码块最好仅被用于保护那些涉及共享状态的操作。
class CooperatingLock {@ThreadSafeclass Taxi {@GuardedBy("this")private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}public void setLocation(Point location) {boolean reachedDestination;synchronized (this) {this.location = location;reachedDestination = location.equals(destination);}if (reachedDestination)dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}@ThreadSafeclass Dispatcher {@GuardedBy("this")private final Set<Taxi> taxis;@GuardedBy("this")private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}public Image getImage() {Set<Taxi> copy;synchronized (this) {copy = new HashSet<Taxi>(availableTaxis);}Image image = new Image();for (Taxi t : copy)image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}
}
使用定时锁
使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超时的时候,tryLock()不会一直等待,而是返回错误信息。
使用tryLock()能够有效避免死锁问题。tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
public class tryLock {public static void main(String[] args) {System.out.println("开始");final Lock lock = new ReentrantLock();new Thread() {@Overridepublic void run() {String tName = Thread.currentThread().getName();if (lock.tryLock()) {System.out.println(tName + "获取到锁!");} else {System.out.println(tName + "获取不到锁!");return;}try {for (int i = 0; i < 5; i++) {System.out.println(tName + ":" + i);}Thread.sleep(5000);} catch (Exception e) {System.out.println(tName + "出错了!!!");} finally {System.out.println(tName + "释放锁!!");lock.unlock();}}}.start();new Thread() {@Overridepublic void run() {String tName = Thread.currentThread().getName();if (lock.tryLock()) {System.out.println(tName + "获取到锁!");} else {System.out.println(tName + "获取不到锁!");return;}try {for (int i = 0; i < 5; i++) {System.out.println(tName + ":" + i);}} catch (Exception e) {System.out.println(tName + "出错了!!!");} finally {System.out.println(tName + "释放锁!!");lock.unlock();}}}.start();System.out.println("结束");}
}
总结
发生死锁的原因主要由于:
-
线程之间交错执行
解决:以固定的顺序加锁
-
执行某方法时就需要持有锁,且不释放
解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
-
永久等待
解决:使用
tryLock()定时锁,超时则返回错误信息
相关文章:
多线程——死锁
死锁 在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。 我们只能通过中止并重启的方式来让程序重新执行。 这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生! 死锁的原因…...
链路追踪可视化利器之火焰图
随着现代化技术的发展,为了能够保证 IT 系统的稳定性、高扩容性,企业往往采用分布式的方式来构建 IT 系统。但也正因为如此,IT 系统中涉及到的服务和组件可能被分布在不同的服务器、数据中心甚至不同的地理位置,这导致应用发生故障…...
C语言 ——— 条件编译指令实际用途
目录 前言 头文件被包含的方式 嵌套文件包含 使用条件编译指令规避头文件多次包含 还有一个编译指令,同样能做到以上功能 前言 条件编译指令多用于对头文件的定义和判断以及删除 头文件被包含的方式 本地文件包含(也就是自己创建的头文件ÿ…...
备战软考Day01-计算机系统
1.数值及其转化 1.数值转化(十进制) 2.十进制推广 3.进制转化 4.数据表示 1.原码 2.反码 3.补码 4.移码 5.定点数 就是小数点的位置固定不变的数。小数点的位置通常有两种约定方式:定点整数(纯整数,小数点在最低有效数值位之后…...
从C语言过渡到C++
📔个人主页📚:秋邱-CSDN博客☀️专属专栏✨:C 🏅往期回顾🏆:单链表实现:从理论到代码-CSDN博客🌟其他专栏🌟:C语言_秋邱的博客-CSDN博客 目录 …...
Docker 的安装和使用
参考资料: 通俗易懂了解什么是docker?Docker 教程 | 菜鸟教程Ubuntu 22.04 安装 DockerDocker 超详细基础教程WSL2 支持 systemctl 命令systemd 和 systemctl 是什么?使用正确的命令重启 WSL 子系统Ubuntu 修改源镜像方法Docker 中出现 ‘/etc/resolv.…...
鸿蒙轻内核A核源码分析系列七 进程管理 (2)
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 轻内核A核源码分析系列一 数据结构-双向循环链表 轻内核A核源码分析系列二 数据结构-位图操作 轻内核A核源码分析系列三 物理内存(1࿰…...
关于TypeScript使用讲解
TypeScript讲解 安装环境 1.安装node js 配置环境变量 2.在终端中 运行 npm i -g typescript typescript: 用于编译ts代码 提供了 tsc命令 实现了将 TS>>>> JS转换 验证: tsc -v 编译并运行 TS代码 1.创建ts文件(TS文件为后缀名的文件࿰…...
C语言 | Leetcode C语言题解之第393题UTF-8编码验证
题目: 题解: static const int MASK1 1 << 7; static const int MASK2 (1 << 7) (1 << 6);bool isValid(int num) {return (num & MASK2) MASK1; }int getBytes(int num) {if ((num & MASK1) 0) {return 1;}int n 0;in…...
Netty权威指南:Netty总结-编解码与序列化
第四章 TCP粘包/拆包问题 4.1 TCP 粘包/拆包 TCP是流协议,也就是没有界限的的一串数据,底层并不知道上层业务数据的具体含义,也就是说一个完整的包可能会被拆分成多个包进行发送,也可能把几个小包封装成一个大的数据包发送。这就…...
FIDAVL:基于视觉语言模型的假图像检测与归因 !
FIDAVL:基于视觉语言模型的假图像检测与归因 ! 这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 作者提出了FIDAVL:使用视觉语言模型进行虚假图像检测。FIDAVL是一…...
如何通过海外云手机提升运营效率
随着技术的不断进步,市场上出现了越来越多的提高跨国电商运营效率的应用,海外云手机就是其中一个。海外云手机的优势体现在多个方面,那么如何通过使用海外云手机来提升运营效率?可以从以下几个方面了解。 首先,海外云手…...
数据库4个范式的说明
在数据库设计中,范式(Normal Form)用于消除冗余和异常,确保数据一致性。以下是第一范式、第二范式、第三范式和BCNF(Boyce-Codd Normal Form,即第四范式)的示例说明: 1. 第一范式&a…...
Excel怎么截图?快速捕捉工作表的多种方法
大家好,这里是效率办公指南! 📸 在日常工作中,我们经常需要对Excel工作表进行截图,无论是为了记录数据、制作演示还是进行数据对比。今天,我们就来学习几种在Excel中截图的方法以及它们的快捷键。 一、使…...
MyBatis动态SQL标签总结、开发手册、高阶用法(动态SQL、OGNL、批量操作、片段重用、 SQL 组合、 执行优化、嵌套查询与延迟加载)
MyBatis提供了一个非常强大的动态SQL功能,它使用了一组XML标签来帮助我们根据不同条件生成动态SQL。动态SQL的设计让开发者可以根据业务需求,灵活地构建SQL查询语句。以下是MyBatis动态SQL标签的总结。 动态SQL标签说明特点<if>条件判断语句&…...
出处不详 取数游戏
目录 取数游戏题目描述背景输入输出数据范围 题解解法优化 打赏 取数游戏 题目描述 背景 两人将 n n n个正整数围成一个圆环,规则如下: 第一名玩家随意选取数字;第二名玩家从与第一名玩家相邻的两个数字中选择一个;而后依次在…...
拉取ros2_control_demos存储库
目录 克隆存储库 方法 1: 使用 git clone 和 rosdep 安装依赖 方法 2: 使用 vcs 工具管理多个存储库 区别总结 rosdep 和 APT 的关系 网络问题 安装依赖 克隆存储库 方法 1: 使用 git clone 和 rosdep 安装依赖 下载存储库: mkdir -p ~/ros2_ws/src cd ~/ros…...
Apache Doris Flink Connector 24.0.0 版本正式发布
亲爱的社区伙伴们,Apache Doris Flink Connector 24.0.0 版本已于 2024 年 9 月 5 日正式发布。该版本新增了对 Flink 1.20 的支持,并支持通过 Arrow Flight SQL 高速读取 Doris 中数据。此外,整库同步所依赖的 FlinkCDC,也需升级…...
语音控制小夜灯的实现方案介绍
语音控制小夜灯的实现方案组成部分 语音控制小夜灯的实现方案主要包括硬件组装和软件编程两个部分。 硬件组装涉及将语音声控模块、灯泡、USB连接线等组件正确连接。首先,使用螺丝刀和螺丝将四个隔离柱固定在底板四个拐角处,同时将语音声控模…...
万龙觉醒免费辅助:VMOS云手机辅助巴克尔阵容搭配攻略!
《万龙觉醒》是一款策略类手游,选择合适的英雄阵容搭配能够极大提升战斗效果。而借助VMOS云手机的辅助功能,玩家可以更加轻松地管理游戏进程,优化操作体验。以下是VMOS云手机的三大核心功能,帮助你更好地掌控《万龙觉醒》战局。 V…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
结构化文件管理实战:实现目录自动创建与归类
手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题,进而引发后续程序异常。使用工具进行标准化操作,能有效降低出错概率。 需要快速整理大量文件的技术用户而言,这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB,…...
【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...
