Java进阶篇--死锁

news2026/4/1 20:30:13

目录

导致死锁的原因

避免死锁的方法

代码示例


死锁是指两个或多个线程在互相等待对方释放资源的情况下,无法继续执行的状态。当发生死锁时,线程将永远阻塞,程序也无法正常完成。

导致死锁的原因

死锁是多线程编程中的一种常见问题,它发生在两个或多个线程彼此持有对方所需资源的情况下,导致它们都无法继续执行。这些线程被称为相互等待对方的资源,从而形成了死锁状态。

导致死锁的原因通常可以归结为以下四个必要条件的同时满足:

  1. 互斥条件(Mutual Exclusion):一个资源每次只能被一个线程占用。当一个线程获得了资源的独占权后,其他线程就无法再访问该资源,直到该线程释放资源。
  2. 请求与保持条件(Hold and Wait):一个线程在持有了至少一个资源的同时又请求其他资源,而这些资源被其他线程所占用。当这种情况发生时,如果不能立即获得所需的资源,就会阻塞并等待其他线程释放资源。
  3. 不可剥夺条件(No Preemption):资源只能在线程自愿释放时才能被其他线程占用,其他线程无法强行将其剥夺。即线程需要使用完所有获取到的资源后才会主动释放资源,不会被其他线程打断。
  4. 循环等待条件(Circular Wait):存在多个线程之间形成一个环形链,每个线程都在等待下一个线程所持有的资源。例如,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,以此类推,直到线程 N 等待线程 A 持有的资源。

只有当以上四个条件同时满足时,死锁才会发生。解决死锁问题的方法通常包括破坏其中一个或多个必要条件。

避免死锁的方法

避免死锁是多线程编程中重要的问题之一,下面介绍几种常见的避免死锁的方法:

  1. 加锁顺序:确保所有线程以相同的顺序获得锁。通过强制要求线程按照特定的顺序获取资源锁,可以避免循环等待条件的发生。
  2. 加锁时限:对获取不到所需资源的线程设置一个超时时间,在超过一定时间后如果仍未获取到资源,则释放已经占用的资源,避免持有并等待条件的发生。这种方法需要谨慎使用,需要根据具体场景来确定超时时间的合理性,并避免引入新的问题。
  3. 死锁检测:实时监控程序运行状态,检测是否存在死锁。一旦检测到死锁,系统可以采取一些恢复措施,例如强制释放某些资源或者重启线程等。
  4. 资源分配策略:通过合理的资源分配策略来预防死锁。例如,银行家算法(Banker's Algorithm)用于在分布式系统中避免死锁,通过动态分配资源并根据系统需要进行资源回收。
  5. 避免持有并等待条件:线程在请求新的资源之前释放已占有的资源。这可以通过设计合适的资源分配算法来实现,例如按照资源请求层级来分配资源,或者采用资源预先分配的方式。
  6. 使用互斥量和条件变量:互斥量和条件变量是常用的同步机制,在使用时需要合理地获取和释放锁,以遵循加锁顺序,避免死锁的发生。

需要注意的是,以上方法并非适用于所有情况,具体的应用需要根据具体的需求和场景来选择合适的策略。在设计多线程程序时,合理的资源管理和同步机制是避免死锁问题的关键。同时,定期进行代码审查、测试和性能优化也是保证程序健壮性和可靠性的重要手段。

代码示例

以下是同时包含死锁和避免死锁的代码示例,

public class main {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) { // 获取资源1的锁
                System.out.println("线程 1:持有资源 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 1:等待资源 2");
                synchronized (resource2) { // 尝试获取资源2的锁
                    System.out.println("线程 1:持有资源 1 和资源 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource1) { // 尝试获取资源1的锁
                System.out.println("线程 2:持有资源 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 2:等待资源 2");
                synchronized (resource2) { // 获取资源2的锁
                    System.out.println("线程 2:持有资源 1 和资源 2");
                }
            }
        });

        thread1.start();
        thread2.start();

        // 等待线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 避免死锁的示例
        Object resourceA = new Object();
        Object resourceB = new Object();

        Thread thread3 = new Thread(() -> {
            synchronized (resourceA) { // 获取资源A的锁
                System.out.println("线程 3:持有资源 A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 3:等待资源 B");
                synchronized (resourceB) { // 尝试获取资源B的锁
                    System.out.println("线程 3:持有资源 A 和资源 B");
                }
            }
        });

        Thread thread4 = new Thread(() -> {
            synchronized (resourceA) { // 获取资源A的锁
                System.out.println("线程 4:持有资源 A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 4:等待资源 B");
                synchronized (resourceB) { // 尝试获取资源B的锁
                    System.out.println("线程 4:持有资源 A 和资源 B");
                }
            }
        });

        thread3.start();
        thread4.start();
    }
}

在这个例子中,一开始的两个线程 thread1 和 thread2 尝试获取 resource1 和 resource2 的锁以形成死锁。为了避免死锁,我们在这两个线程中修改了获取资源的顺序。其中一个线程先获取 resource1,另一个线程再获取 resource1,这样能够保证资源的获取顺序是一致的,避免了循环等待。

在后面的代码示例中,我们通过尝试获取不同的资源来模拟死锁情况。为了避免死锁,我们采取了避免持有并请求条件的策略,即一个线程只能在释放所有资源之后再请求新的资源。这样能够确保资源的占用和释放是一致的,不会出现死锁的情况。

注意,在上面的示例中,为了能够观察到死锁和避免死锁的效果,我们需要等待线程执行完成。因此,我们在两个线程启动之后使用 join() 方法来等待它们执行完成。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行情分析——加密货币市场大盘走势(10.16)

目前大饼再次止稳,并开始向上攀升,目前MACD来看也是进入了多头趋势。重新调整了蓝色上涨趋势线,目前来看这次的低点并没有跌破上一个低点,可以认为是上涨的中继。注意白天的下跌回调。 以太目前也是走了四连阳线,而MAC…

网络安全—小白自学笔记

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高; 二、则是发展相对成熟…

你知道跨境商城源码如何为商家节省成本和时间吗

跨境电商行业迅速发展,商家如何利用跨境商城源码实现成本和时间节省 在全球经济一体化的背景下,跨境电商行业蓬勃发展,为商家提供了全球范围的市场机会。然而,面临的挑战也日益增多,比如高昂的运营成本和繁琐的流程&am…

嵌入式面试经典30问

嵌入式面试经典30问 很多同学说很害怕面试,看见面试官会露怯,怕自己的知识体系不完整,怕面试官考的问题回答不上了,所以今天为大家准备了嵌入式工程师面试经常遇到的30个经典问题,希望可以帮助大家提前准备&#xff0…

weapp-tailwindcss for uni-app 样式条件编译语法插件

weapp-tailwindcss for uni-app 样式条件编译语法插件 版本需求 2.10.0 weapp-tailwindcss for uni-app 样式条件编译语法插件 这是什么玩意?如何使用 tailwind.config.js 注册postcss 插件注册 uni-app vite vue3uni-app vue2 配置完成 配置项 这是什么玩意? 在 uni-app …

GeoServer源码运行(数据目录+数据库)

1、源码下载 下载地址:https://github.com/geoserver/geoserver/tree/2.23.2 图 2选择版本下载 2、启动配置 图 3主程序启动类配置 GeoServer主程序的启动类为web->app[gs-web-app]模块下test目录下“org.geoserve

3个g的文件怎么发送给别人?三种方法自由选择!

发送大文件不仅会耗费较长时间,同时也可能需要消耗更多的流量费用,更容易出现网络中断或其他传输错误。这时候就需要使用文件压缩工具将它们压缩为一个文件,然后将其发送到收件人。下面介绍了三种大体积压缩的方法,一起来看看吧&a…

探索未来:硬件架构之路

文章目录 🌟 硬件架构🍊 基本概念🍊 设计原则🍊 应用场景🍊 结论 📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

微信小程序里配置less

介绍 在微信小程序里,样式文件的后缀名都是wxss,这导致一个问题,就是页面样式过多的时候,要写很多的类名来包裹,加大了工作量,还很有可能会写错样式。这时可以配置一个less,会大大提高代码编辑…

Programming abstractions in C阅读笔记:p179-p180

《Programming Abstractions In C》学习第60天,p179-p180总结。 一、技术总结 1.palindrome(回文) (1)包含单个字符的字符串(如"a"),或者空字符串(如" ")也是回文。 (2)示例:“level”、“noon”。 2.predicate fun…

优维产品使用最佳实践:实例拓扑

背 景 实例拓扑可以帮助我们直观地了解整个系统的架构和组成情况,该拓扑图是通过已有的实例的关联关系自动生成,当实例数据和关系变化时拓扑图也能实时更新,我们可以快速直观的查看当前实例下所有资源的之间的网状关系和资源数量。 实例拓扑…

ChatGPT DALL-E 3的系统提示词大全

每当给出图像的描述时,使用dalle来创建图像,然后用纯文本总结用于生成图像的提示。如果用户没有要求创建特定数量的图像,默认创建四个标题,这些标题应尽可能多样化。发送给Dalle的所有标题都必须遵循以下策略:1.如果描…

探索低代码技术

低/无代码的高速发展,属于软件市场的选择,相较于传统编写代码的开发方式,低/无代码开发效率高、投入成本低、技术门槛也更低,未来更多软件应用将使用低/无代码技术完成,这也是趋势。 身为开发人员经常需要花大量时间在…

2011年408真题复盘

紫色标记是认为有一定的思维难度或重点总结 红色标记是这次刷真题做错的 记录自己对题目的一些想法与联系,可能并不太关注题目本身。 分数用时 选择部分10.14 78/8037min大题部分10.1456/7080min总分134117min 摘自知乎老哥:“我做历年真题时&#xff0c…

VR、AR、MR、XR到底都是什么?有什么区别

目录 VRARMRXRAR、VR、MR、XR的区别 VR 英:Virtual Reality 中文翻译:虚拟现实 又称计算机模拟现实。是指由计算机生成3D内容,为用户提供视觉、听觉等感官来模拟现实,具有很强的“临场感”和“沉浸感”。我们可以使用耳机、控制器…

drawio简介以及下载安装

drawio简介以及下载安装 drawio是一款非常强大的开源在线的流程图编辑器,支持绘制各种形式的图表,提供了 Web端与客户端支持,同时也支持多种资源类型的导出。 访问网址:draw.io或者直接使用app.diagrams.net直接打开可以使用在线版…

前端设计模式应应用场景

前端设计模式应应用场景 创建型模式(Creational Patterns)工厂模式单例模式原型模式 行为型模式(Behavioral Patterns)策略模式观察者模式/发布订阅模式迭代器模式状态模式 结构型模式(Structural Patterns)装饰器模式代理模式 创建型模式(Creational Patterns) 处理对象的创建…

基于FPGA的图像高斯滤波实现,包括tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a vivado2019.2 3.部分核心程序 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 202…

尚硅谷Docker核心技术

目录 第1课时 docker_前提知识要求和课程简介第2课时 docker_为什么会出现第3课时 docker_理念第4课时 docker_是什么?第5课时 docker_能干什么第6课时 docker_3要素第7课时 centos6安装Dockercentos7安装Docker第9课时 阿里云镜像加速器配置第10课时 helloworld镜像…

FPGA project : flash_continue_write

本实验学习了通过spi通信协议,驱动flash;完成连续写操作。 连续写: 本质上还是页编程指令,两种连续写的方式: 1,每次只写1byte的数据。 2,每次写满1页数据,计算剩余数据够不够写…