”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 多线程概念 部分死锁

多线程概念 部分死锁

发布于2024-11-15
浏览:186

欢迎来到我们的多线程系列的第 3 部分!

  • 在第 1 部分中,我们探讨了原子性不变性
  • 在第 2 部分中,我们讨论了饥饿

在这一部分中,我们将深入研究多线程中死锁的机制。原因是什么,如何识别以及可以使用的预防策略,以避免将代码变成僵局。应用程序逐渐停止,通常没有任何明显的错误,让开发人员感到困惑,系统冻结。

Multithreading Concepts Part  Deadlock

探索并发的复杂轨迹

理解死锁的一个有用的类比是想象一个铁路网络,在相交的轨道上有多列火车。

由于每列火车都在等待下一列火车开动,因此没有一列火车可以继续行驶,从而导致僵局。在这种情况下,低效的信号系统允许每列列车进入各自的路段,而无需先确认下一段是否空闲,从而将所有列车陷入牢不可破的循环中。

这个火车示例说明了多线程中的典型死锁,其中线程(如火车)在等待其他资源被释放时保留资源(轨道部分),但没有一个可以前进。为了防止这种软件死锁,必须实施有效的资源管理策略(类似于更智能的铁路信号),以避免循环依赖并确保每个线程的安全通道。

1.什么是死锁?

死锁是一种线程(或进程)无限期阻塞、等待其他线程持有的资源的情况。这种情况会导致无法打破的依赖关系循环,任何涉及的线程都无法取得进展。在探索检测、预防和解决方法之前,了解死锁的基础知识至关重要。

2. 死锁发生的条件

要发生死锁,必须同时满足四个条件,称为科夫曼条件:

  • 互斥: 至少一个资源必须以不可共享模式保存,这意味着一次只有一个线程可以使用它。

  • 持有并等待:线程必须持有一种资源,并等待获取其他线程持有的其他资源。

  • 无抢占: 不能从线程中强行夺走资源。他们必须自愿释放。

  • 循环等待: 存在一个封闭的线程链,其中每个线程至少拥有链中下一个线程所需的一个资源。

Multithreading Concepts Part  Deadlock

我们理解为时序图

Multithreading Concepts Part  Deadlock

在上面的动画中,

  • 线程A持有资源1并等待资源2
  • 当线程B持有资源2并等待资源1时

上面共享的死锁的所有四个条件都存在,这会导致无限期的阻塞。打破其中任何一个都可以防止死锁。

3. 检测/监控死锁

检测死锁,尤其是在大规模应用程序中,可能具有挑战性。但是,以下方法可以帮助识别死锁

  • 工具:Java的JConsole、VisualVM以及IDE中的线程分析器可以实时检测死锁。
  • 线程转储和日志:分析线程转储可以揭示等待线程及其所持有的资源。

有关如何调试/监视死锁的详细概述,请访问使用 VisualVM 和 jstack 调试和监视死锁

4. 预防死锁的策略

  • 应用等待死亡和伤口等待方案
    等待死亡方案:当一个线程请求另一个线程持有的锁时,数据库会评估相对优先级(通常基于每个线程的时间戳)。如果请求线程的优先级较高,则等待;否则,它会死掉(重新启动)。
    Wound-Wait 方案:如果请求线程具有较高优先级,它会通过强制释放锁来伤害(抢占)较低优先级线程。

  • 共享状态的不可变对象 尽可能将共享状态设计为不可变。由于不可变对象无法修改,因此它们不需要锁
    进行并发访问,降低死锁风险并简化代码。

  • 使用带有超时的tryLock来获取锁

    :与标准同步块不同,ReentrantLock允许使用tryLock(timeout,unit)在指定的时间内尝试获取锁。如果在此时间内未获取锁,它将释放资源,防止无限期阻塞。

  • ReentrantLock 锁1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); 公共无效acquireLocks(){ 尝试 { if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) { 尝试 { if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) { // 临界区 } } 最后 { lock2.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } 最后 { lock1.unlock(); } }
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void acquireLocks() {
    try {
        if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                    // Critical section
                }
            } finally {
                lock2.unlock();
            }
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        lock1.unlock();
    }
}

    锁定排序和释放
  • 为锁获取设置严格的全局顺序。如果所有线程以一致的顺序获取锁,则不太可能形成循环依赖,从而避免死锁。例如,在整个代码库中始终先获取 lock1,然后再获取 lock2。这种做法在较大的应用程序中可能具有挑战性,但对于降低死锁风险非常有效。
  • 导入java.util.concurrent.locks.Lock; 导入 java.util.concurrent.locks.ReentrantLock; 公共类LockOrderingExample { 私有静态最终锁lock1 = new ReentrantLock(); 私有静态最终锁lock2 = new ReentrantLock(); 公共静态无效主(字符串[] args){ 线程 thread1 = new Thread(() -> { acquireLocksInOrder(锁1,锁2); }); 线程 thread2 = new Thread(() -> { acquireLocksInOrder(锁1,锁2); }); 线程1.start(); 线程2.start(); } 私有静态无效 acquireLocksInOrder(锁定第一锁,锁定第二锁){ 尝试 { 首先Lock.lock(); System.out.println(Thread.currentThread().getName() "获取了lock1"); SecondLock.lock(); System.out.println(Thread.currentThread().getName() "获得锁2"); // 执行一些操作 } 最后 { SecondLock.unlock(); System.out.println(Thread.currentThread().getName() "释放锁2"); 首先Lock.unlock(); System.out.println(Thread.currentThread().getName() "释放了lock1"); } } }
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void acquireLocks() {
    try {
        if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                    // Critical section
                }
            } finally {
                lock2.unlock();
            }
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        lock1.unlock();
    }
}

  • 使用线程安全/并发集合

    :Java的java.util.concurrent包提供了常见数据结构(ConcurrentHashMap、CopyOnWriteArrayList等)的线程安全实现,可以在内部处理同步,减少需要显式锁。这些集合最大限度地减少了死锁,因为它们旨在使用内部分区等技术来避免显式锁定的需要。

  • 避免嵌套锁

    尽量减少在同一块内获取多个锁以避免循环依赖。如果需要嵌套锁,请使用一致的锁顺序

  • 软件工程师的要点

每当您创建需要锁定的设计时,就有可能出现死锁。
  • 死锁
  • 是由进程之间的依赖关系循环引起的阻塞问题。没有进程可以取得进展,因为每个进程都在等待另一个进程持有的资源,并且没有一个进程可以继续释放资源。
  • 死锁
  • 更为严重,因为它会完全停止所涉及的进程,并且需要打破死锁循环才能恢复。
  • 死锁
  • 仅当有两个不同的锁时才会发生,即当您持有一个锁并等待另一个锁释放时。 (但是,死锁还有更多条件)。
  • 线程安全
  • 并不意味着无死锁。它仅保证代码将根据其接口进行操作,即使是从多个线程调用时也是如此。使类线程安全通常包括添加锁以保证安全执行。
  • 尾奏

无论您是初学者还是经验丰富的开发人员,了解死锁对于在并发系统中编写健壮、高效的代码至关重要。在本文中,我们探讨了死锁是什么、其原因以及预防死锁的实用方法。通过实施有效的资源分配策略、分析任务依赖性以及利用线程转储和死锁检测工具等工具,开发人员可以最大限度地降低死锁风险并优化其代码以实现平滑并发。

当我们继续了解多线程的核心概念时,请继续关注本系列的下一篇文章。我们将深入研究
关键部分

,了解如何在多个线程之间安全地管理共享资源。我们还将讨论竞争条件的概念,这是一种常见的并发问题,如果不加以控制,可能会导致不可预测的行为和错误。 通过每一步,您将更深入地了解如何使您的应用程序线程安全、高效且具有弹性。不断突破多线程知识的界限,构建更好、性能更高的软件!

参考

Stackoverflow
  • 信息图表
  • 如何检测和修复死锁
版本声明 本文转载于:https://dev.to/anwaar/multithreading-concepts-part-3-deadlock-4ip6?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    MySQL配置不正确:相对路径的问题在Django中运行python manage.py runserver时,可能会遇到以下错误:ImproperlyConfigured: Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-...
    编程 发布于2024-11-15
  • 如何在没有 JavaScript 的情况下从整个表格单元格创建超链接?
    如何在没有 JavaScript 的情况下从整个表格单元格创建超链接?
    从表格单元格创建超链接任务是将以下 HTML 标记转换为功能性超链接:<td> some text <div>a div</div> </td>目标是将整个表格数据单元格 () 转换为可点击的链接,而无需借助 JavaScript。首选方法:基...
    编程 发布于2024-11-15
  • 如何在旧版 Internet Explorer 中模拟 getElementsByClassName()?
    如何在旧版 Internet Explorer 中模拟 getElementsByClassName()?
    getElementsByClassName() 的跨浏览器兼容性IE6、IE7 和 IE8 无法使用 getElementsByClassName() 方法,这导致了尝试根据元素的类属性选择元素时面临挑战。但是,有一些解决方案可以克服此限制,而无需依赖 jQuery 等第三方库。在旧版 Inter...
    编程 发布于2024-11-15
  • 除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    无需强制转换即可上下文转换为 bool您的类定义了对 bool 的显式转换,使您能够在条件语句中直接使用其实例“t”。然而,这种显式转换提出了一个问题:“t”在哪里可以在不进行强制转换的情况下用作 bool?上下文转换场景C 标准指定了四种值可以根据上下文转换为的主要场景bool:语句:if、whi...
    编程 发布于2024-11-15
  • 大批
    大批
    方法是可以在对象上调用的 fns 数组是对象,因此它们在 JS 中也有方法。 slice(begin):将数组的一部分提取到新数组中,而不改变原始数组。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index p...
    编程 发布于2024-11-15
  • 如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 识别今天生日的用户使用 MySQL 确定今天是否是用户的生日涉及查找生日匹配的所有行今天的日期。这可以通过一个简单的 MySQL 查询来实现,该查询将存储为 UNIX 时间戳的生日与今天的日期进行比较。以下 SQL 查询将获取今天有生日的所有用户: FROM USERS ...
    编程 发布于2024-11-15
  • 有没有办法在不阻塞的情况下检查Go中的标准输入(os.Stdin)是否有数据?
    有没有办法在不阻塞的情况下检查Go中的标准输入(os.Stdin)是否有数据?
    如何使用 Go 确定 Stdin 中的数据可用性问题在 Go 中,有没有可靠的方法来检查输入流(os .stdin) 包含数据?当没有数据可用时从流块中读取的传统方法,对于某些用例来说是不切实际的。答案类似于其他文件,可以检查 os.Stdin 以确定其大小,为数据可用性检测提供便捷的方法。pack...
    编程 发布于2024-11-15
  • 如何使用 @font-face 规则在 CSS 中正确导入字体?
    如何使用 @font-face 规则在 CSS 中正确导入字体?
    在 CSS 中导入字体:综合解决方案在 Web 开发中,经常需要使用客户端可能未安装的字体电脑。为了实现这一点,CSS 提供了 @font-face 规则。但是,某些用户可能会遇到所提供的代码片段的问题:@font-face { font-family: EntezareZohoor2; ...
    编程 发布于2024-11-15
  • 如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    在 PHP 中组合关联数组在 PHP 中,将两个关联数组组合成一个数组是一项常见任务。考虑以下请求:问题描述:提供的代码定义了两个关联数组,$array1和$array2。目标是创建一个新数组 $array3,它合并两个数组中的所有键值对。 此外,提供的数组具有唯一的 ID,而名称可能重合。要求是构...
    编程 发布于2024-11-15
  • Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta:列偏移的删除和恢复Bootstrap 4 在其 Beta 1 版本中引入了重大更改柱子偏移了。然而,随着 Beta 2 的后续发布,这些变化已经逆转。从 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    编程 发布于2024-11-15
  • JavaScript 中变量赋值左侧的方括号有什么作用?
    JavaScript 中变量赋值左侧的方括号有什么作用?
    解构赋值:揭示变量赋值左侧方括号的含义在 JavaScript 中,在变量赋值左侧遇到方括号变量赋值的左侧可能看起来令人困惑。为了破译这种语法的含义,我们冒险进入解构赋值的领域。语法和操作解构赋值,这是 JavaScript 1.7 和 ECMAScript 6 中引入的功能,使我们能够将数组或对象...
    编程 发布于2024-11-15
  • 对 MySQL 结果进行分组:SQL 与 PHP - 哪种方法最好?
    对 MySQL 结果进行分组:SQL 与 PHP - 哪种方法最好?
    MySQL 按字段数据对结果进行分组:SQL 与 PHP 方法在数据管理领域,可能会出现需要按字段数据对数据库结果进行分组的情况,并且以特定格式显示它们。请考虑以下示例:示例 1:根据组 ID 组织名称列表:示例 2:根据公共组 ID 配对相应的标题和系数值:为了解决这些挑战,出现了两种主要方法:S...
    编程 发布于2024-11-15
  • 如何推迟加载大型 CSS 文件以提高页面性能?
    如何推迟加载大型 CSS 文件以提高页面性能?
    优化CSS交付:了解延迟CSS加载优化CSS交付时,在页面加载后延迟加载大型CSS文件可以显着提高页面性能。虽然 Google 开发人员提供的示例演示了内联小型 CSS 文件以实现关键样式,但它留下了如何推迟较大 CSS 文件的问题。加载后访问原始 CSS 文件要将大型 CSS 文件的加载推迟到页面...
    编程 发布于2024-11-15
  • Javascript 基元实际上是对象吗?
    Javascript 基元实际上是对象吗?
    Javascript 基元与对象:澄清概念尽管普遍认为“Javascript 中几乎所有内容都是对象”,但并非所有内容语言中的实体遵循这个定义。基元和对象之间的区别需要澄清。基元与对象相反,基元是以其基本形式存在的不可变值。它们缺少方法和属性,并包括以下数据类型:StringsNumbersBool...
    编程 发布于2024-11-15
  • 为什么 C++ 中的联合内禁止使用 `std::string` 对象?
    为什么 C++ 中的联合内禁止使用 `std::string` 对象?
    为什么 std::string 在联合中被禁止在 C 编程领域,联合是一种特殊的结构,它允许在联合中存储各种数据类型共享内存地址。然而,当涉及联合中的成员时,有一个有趣的限制:禁止具有非平凡构造函数的类,包括 std::string。非平凡构造函数的问题根本原因可以追溯到工会的性质。联合体中的成员从...
    编程 发布于2024-11-15

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3