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

死锁出现的原因

多线程安全出现的问题:

1.操作系统是随机调度的,抢占式执行

2.代码结构内,多个线程同时修改同一个变量

                        禁止变量修改是以一种解决线程安全问题的思路

                        Java中这种思路的普适性不是很高

3.修改的操作并不是"原子性"

                                通过加锁把非原子性的修改操作打包成一个整体,变成原子操作

4.内存可见性

5.指令重排序

对于问题三引入了synchronized关键字进行加锁

()指定锁对象

{}放进去的就是要打包成一个整体的代码

进入代码开机会针对锁对象加锁

除了代码块(),return,break针对锁对象解锁

具体synchronized使用方法见上一篇文章

死锁出现的场景

1)一个线程一把锁,这个线程针对这把锁连续加锁两次

这种情况下,代码示例没有出现真正的死锁,因为synchronized针对这种情况做出了特殊的处理

"synchronized"是"可重入锁"

Java可重入锁的内部包括"线程持有者"和"计数器"两个信息

        线程持有者:如果某个线程加锁的时候发现锁已经被人占用,但是恰好占用的正是自己,那么仍然可以继续获取到锁,并且让计数器自增

        解锁的时候计数递减为0,才真正的释放锁(才能被被别的线程获取)

2.两个线程两把锁

死锁代码如下:

    private static Object Locker1=new Object();private static Object Locker2=new Object();public static void main(String[] args) {Thread thread1=new Thread(()-> {synchronized(Locker1){for(int i=0;i<50000;i++){}synchronized(Locker2){}}});Thread thread2=new Thread(()->{synchronized(Locker2){for(int i=0;i<50000;i++){//需要有东西在第一把锁里}synchronized(Locker1){}}});thread1.start();thread2.start();}

线程1针对locker1加锁,线程针对locker2加锁

线程1不释放locker1的情况下再针对locker2加锁

同时线程2在不释放locker2的情况下再对locker1加锁

两个内部锁都没执行到,说明这两个现场第二次synchronized的时候阻塞了

死锁的场景3

3.N个线程M个锁

多个线程形成环形依赖,每个线程持有部分资源并等待下一个线程的资源。

public class CircularDependencyDeadlock {private static final Object lockA = new Object();private static final Object lockB = new Object();private static final Object lockC = new Object();public static void main(String[] args) {// 线程1:持有 lockA,需要 lockBThread thread1 = new Thread(() -> {synchronized (lockA) {System.out.println("Thread1 持有 lockA");try {Thread.sleep(100); // 确保其他线程能获取到各自的锁} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println("Thread1 获取到 lockB");}}});// 线程2:持有 lockB,需要 lockCThread thread2 = new Thread(() -> {synchronized (lockB) {System.out.println("Thread2 持有 lockB");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockC) {System.out.println("Thread2 获取到 lockC");}}});// 线程3:持有 lockC,需要 lockAThread thread3 = new Thread(() -> {synchronized (lockC) {System.out.println("Thread3 持有 lockC");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockA) {System.out.println("Thread3 获取到 lockA");}}});// 启动所有线程thread1.start();thread2.start();thread3.start();}
}
  • 线程1:持有锁A,等待锁B

  • 线程2:持有锁B,等待锁C

  • 线程3:持有锁C,等待锁A

  • 三者形成环形依赖链,彼此无法继续执行。

死锁形成的四个必要条件(缺一不可)

1.锁是互斥[基本特性]:资源(锁,文件,设备)一次只能被一个线程独占使用

2.锁是不可被抢占的[基本特性]:线程1拿到了锁A,如果线程1不释放A,线程2无法拿到锁A

3.请求和保持

 Thread thread1=new Thread(()-> {synchronized(Locker1){for(int i=0;i<50000;i++){}synchronized(Locker2){}}});Thread thread2=new Thread(()->{synchronized(Locker2){for(int i=0;i<50000;i++){//需要有东西在第一把锁里}synchronized(Locker1){}}});thread1.start();thread2.start();

保持:没有释放锁的情况下

请求:获取其他的锁

解决方法:获取其他锁前,先释放自己的锁(避免锁嵌套)

4.循环等待/循环依赖/环路等待

多个线程形成环形依赖,每个线程持有部分资源并等待下一个线程的资源。造成阻塞

解决办法:给锁编号(1,2,3,4......n)约定所有线程在加锁的时候,按照一定的顺序进行加锁.(约定加锁顺序)

死锁话题下还有一个解决方案,银行家算法

        银行家算法过于复杂,日常开发中实现一套银行家算法费时费力,哪怕实现也许银行家算法本身就有bug,所以日常开发中不会使用这种方法来解决死锁问题

http://www.xdnf.cn/news/16396.html

相关文章:

  • 《计算机组成原理与汇编语言程序设计》实验报告四 Debug及指令测试
  • #影·数学计划# N1 一元一次方程讲解 未完待续
  • 基于STM32的智能康养木屋监测系统
  • vector使用和模拟
  • 在本地环境中运行 ‘dom-distiller‘ GitHub 库的完整指南
  • openshift AI 2.22安装的需求
  • 人工智能与城市:城市生活的集成智能
  • 基于 LSTM 与 SVM 融合的时间序列预测模型:理论框架与协同机制—实践算法(1)
  • Wireshark TS | 发送数据超出接收窗口
  • Frontiers in Psychology投稿LaTeX(三)
  • 元宇宙中的“虫洞“:技术实现、应用场景与未来挑战
  • J3160迷你小主机 性能测试 对比i3-4170 以及服务器
  • Python Pandas.qcut函数解析与实战教程
  • RS485转profinet网关如何让JRT激光测距传感器开启自动模式连续测量模式
  • 数据结构基础内容(第九篇:最短路径)
  • DP之背包基础
  • AutoLabelImg:高效的数据自动化标注工具和下载
  • Gradio.NET 中文快速入门与用法说明
  • 2025年7月25日-7月26日 · AI 今日头条
  • 在Luckfox Lyra(Zero W)上将TF卡格式化为ext4文件系统
  • 《 集成异步任务与定时调度:线程池与任务中心设计》
  • AI与区块链Web3技术融合:重塑数字经济的未来格局
  • 2025年项目数据看板工具选型指南,精选12款
  • SQL中的group by和having区别详解
  • 【C语言网络编程】HTTP 客户端请求(基于 Socket 的完整实现)
  • 神经网络知识讨论
  • 网易大模型算法岗面经80道
  • 【学习笔记】MimicGen: 基于人类演示的可扩展机器人学习数据生成系统
  • 批量重命名带编号工具,附免费地址
  • idea打开后project窗口未显示项目名称的解决方案