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

【多线程】死锁

【多线程】死锁

本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】互斥锁(Mutex)是什么?
2.【多线程】临界区(Critical Section)是什么?
3.【多线程】计算机领域中的各种锁
4.【多线程】信号量(Semaphore)是什么?
5.【多线程】信号量(Semaphore)常见的应用场景
6.【多线程】条件变量(Condition Variable)是什么?
7.【多线程】监视器(Monitor)是什么?
8.【多线程】什么是原子操作(Atomic Operation)?
9.【多线程】竞态条件(race condition)是什么?
10.【多线程】无锁数据结构(Lock-Free Data Structures)是什么?
11.【多线程】线程休眠(Thread Sleep)的底层实现
12.【多线程】多线程的底层实现
13.【多线程】读写锁(Read-Write Lock)是什么?
14.【多线程】死锁

死锁是并发编程中一个非常经典且棘手的问题。它描述了一种特定的僵局状态,可以类比为一个现实生活中的场景:

一个生动的比喻:餐桌上的哲学家

想象一下,一张圆桌坐着五位哲学家,他们只做两件事:思考吃饭。桌上只有五支筷子,每两位哲学家之间放一支。

在这里插入图片描述

规则是:

  1. 哲学家必须用左右两边的筷子才能吃饭。
  2. 他必须先拿起左边的筷子,再拿起右边的筷子。
  3. 如果有一支筷子被别人拿着,他就必须等待。
  4. 吃完后,他会同时放下两支筷子。

现在,考虑这样一种极端情况:
在某个时刻,所有五位哲学家同时决定要吃饭。他们每个人都按照规则,先拿起了自己左边的筷子。

结果就是:

  • 现在每支筷子都被一个人拿着。
  • 每个哲学家都在等待自己右边的筷子被释放。
  • 但右边的筷子被自己右边的人拿着,而那个人也在等待他右边的筷子…

于是,所有哲学家都永远地等待下去,没有人能开始吃饭,也没有人会放下自己手中的筷子。 这就是死锁。


死锁的正式定义

在计算机科学中,死锁 是指两个或两个以上的并发进程(或线程),在彼此等待对方释放所占有的资源,但在没有外部干预的情况下,所有进程都无法继续推进的一种状态。

产生死锁的四个必要条件

死锁的发生必须同时满足以下四个条件,缺一不可:

  1. 互斥:一个资源每次只能被一个进程(或线程)使用。

    • 就像一支筷子一次只能被一个哲学家使用。
  2. 持有并等待:一个进程在等待其他资源的同时,继续持有已分配到的资源。

    • 就像每个哲学家都拿着左边的筷子(持有),同时在等待右边的筷子(等待)。
  3. 不可剥夺:进程已获得的资源,在未使用完之前,不能被强行剥夺。

    • 你不能强行从哲学家手中抢走他已经拿起的筷子。
  4. 循环等待:存在一个进程资源的环形等待链。进程P1等待P2占有的资源,P2等待P3占有的资源,……,Pn等待P1占有的资源。

    • 就像五位哲学家形成了一个等待的圆圈。

一个简单的代码示例

以下是一个极简的Java代码示例,演示了两个线程如何发生死锁:

public class SimpleDeadlock {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread threadA = new Thread(() -> {synchronized (lock1) { // 线程A获取了lock1System.out.println("Thread A: Holding lock 1...");try { Thread.sleep(10); } catch (InterruptedException e) {}System.out.println("Thread A: Waiting for lock 2...");synchronized (lock2) { // 线程A在等待lock2System.out.println("Thread A: Acquired both locks!");}}});Thread threadB = new Thread(() -> {synchronized (lock2) { // 线程B获取了lock2System.out.println("Thread B: Holding lock 2...");try { Thread.sleep(10); } catch (InterruptedException e) {}System.out.println("Thread B: Waiting for lock 1...");synchronized (lock1) { // 线程B在等待lock1System.out.println("Thread B: Acquired both locks!");}}});threadA.start();threadB.start();}
}

运行结果可能是:

Thread A: Holding lock 1...
Thread B: Holding lock 2...
Thread A: Waiting for lock 2...
Thread B: Waiting for lock 1...
// ... 然后程序就卡在这里,永远不会结束

分析:

  • 线程A持有lock1,并请求lock2
  • 线程B持有lock2,并请求lock1
  • 它们互相等待对方释放锁,形成了循环等待,导致死锁。

如何处理和预防死锁?

既然死锁需要四个条件同时成立,那么破坏其中任意一个条件就可以预防死锁。

  1. 破坏“互斥”

    • 这通常很难,因为像打印机、数据库写操作等资源本质上是需要互斥的。但对于一些只读操作,可以设计为共享访问。
  2. 破坏“持有并等待”

    • 一次性申请所有资源:线程在开始执行前,必须一次性申请它所需要的所有资源。如果无法全部满足,它就什么资源都不占有,直接等待。这避免了在等待时还占着别的资源。
  3. 破坏“不可剥夺”

    • 如果一个线程已经持有了一些资源,但又无法申请到新的资源,那么它必须释放所有已占有的资源,以后需要时再重新申请。
  4. 破坏“循环等待”(最常用和实用的方法):

    • 按顺序申请资源:给所有资源类型定义一个全局的线性顺序。每个线程都必须严格按照这个顺序来申请资源。
    • 在上面的代码例子中,如果强制线程A和线程B都必须先申请lock1,再申请lock2,那么死锁就不会发生。因为当线程B想申请lock1时,它发现lock1已经被A占用了,它就会被阻塞,并释放它已经占有的任何资源(在这个策略下,它此时不应该占有任何资源),从而打破了循环等待链。

总结

死锁是多线程编程中一个必须警惕的“陷阱”。它源于多个执行流对有限资源的竞争和不当的获取顺序。理解其产生的四个必要条件,并采取相应的预防策略(尤其是按顺序获取锁),是编写健壮、可靠的多线程程序的关键。在复杂系统中,还需要借助工具来检测和分析潜在的死锁。

http://www.dtcms.com/a/457983.html

相关文章:

  • 自学阿里云认证,能救一个是一个!
  • 买域名做网站跳转网新科技集团有限公司
  • 关于网站维护的书籍商务网站规划建设与管理答案
  • 【C语言基础详细版】03. 函数详解:从概念到高级应用
  • 涿州做网站公司阿里云网站怎么备案域名
  • 网站制作设计机构贵阳企业网站建设
  • wordpress快速仿站教程建立一个网站需要哪些
  • Linux 进程分身术:fork() 函数的深度解析
  • 小程序建站公司app开发需要多少费用
  • 贪心算法详解:从入门到精通(C++实现)
  • 颜群JVM【04】助记符
  • 网站优化 推广重庆教育建设集团有限公司网站
  • Manjaro 系统下 PCManFM 挂载 NTFS 分区报错:从踩坑到彻底解决
  • 单片机使用串口(usart) , 封装( print )函数 .
  • 外贸网站建设和优化做网站要不要学ps
  • 福建省建设厅网站 企业三网一体网站建设
  • 湖南做网站大连凯杰建设有限公司网站
  • 吴恩达机器学习课程(PyTorch适配)学习笔记:2.4 激活函数与多类别处理
  • 【PAG】PAG简介
  • hutool交并集
  • 赣州建设公司网站权威网站有哪些
  • Python制作12306查票工具:从零构建铁路购票信息查询系统
  • 《道德经》第十三章
  • 东莞做网站网络公司官网建设的重要性
  • Docker 容器操作
  • 小说网站建设源码潜江网络
  • 做网页游戏网站html网页设计大赛作品
  • 日语学习-日语知识点小记-进阶-JLPT-N1阶段应用练习(8):语法 +考え方21+2022年7月N1
  • 维基框架 (Wiki Framework) v1.1.2 | 企业级微服务开发框架
  • 做的网站提示不安全个人网站的名字