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

理解死锁:场景、实例与预防策略

什么是死锁

死锁(Deadlock) 是指两个或多个线程在运行过程中,由于争夺资源而造成一种相互等待的现象,导致它们都无法继续执行。
死锁通常发生在多线程程序中,当多个线程都试图获取对方持有的资源时,就可能陷入“你等我,我等你”的状态,程序因此进入僵局。

在 Java 中,死锁多见于同时持有多把锁的场景,例如使用 synchronizedReentrantLock 时多个线程互相抢占资源。


死锁实例代码

public class DeadlockDemo {private final Object lock1 = new Object();private final Object lock2 = new Object();public void thread1() {synchronized (lock1) {System.out.println("Thread1: 持有lock1");try { Thread.sleep(50); } catch (InterruptedException e) {}synchronized (lock2) {System.out.println("Thread1: 持有lock1和lock2");}}}public void thread2() {synchronized (lock2) {System.out.println("Thread2: 持有lock2");try { Thread.sleep(50); } catch (InterruptedException e) {}synchronized (lock1) {System.out.println("Thread2: 持有lock2和lock1");}}}public static void main(String[] args) {DeadlockDemo demo = new DeadlockDemo();new Thread(demo::thread1).start();new Thread(demo::thread2).start();}
}两个线程互相等待对方的锁,导致程序死锁。

死锁产生的四个必要条件

死锁(Deadlock)是一种 多个线程或进程互相占有资源、相互等待,导致都无法继续运行的状态。
它必须同时满足这4个条件:


1. 互斥条件(Mutual Exclusion)

  • 解释

    • 某个资源在同一时间只能被一个线程占用
    • 其他线程只能等待该资源被释放后才能访问。
    • 例如:两个线程都想打印日志,但打印机只有一个。
  • Java 中的体现

    • synchronized 块、ReentrantLock、文件句柄等都是互斥资源。
  • 打破策略

    • 共享资源:如果可以让多个线程“共享读”,可以用 读写锁ReentrantReadWriteLock),允许多个线程同时读。
    • 无锁化:用 CAS (Compare-And-Swap) 这种原子操作替代锁,比如 Java 的 AtomicInteger
    • 乐观并发:像数据库的乐观锁一样,认为冲突很少发生,先不加锁,冲突时再回滚。

2. 请求与保持条件(Request and Maintain)

  • 解释

    • 线程 已经持有一个资源,但它还想申请其他资源,而且在申请新资源的同时不释放已持有的资源
    • 例如:线程 A 拿着资源 X,要资源 Y;线程 B 拿着资源 Y,要资源 X。
  • Java 中的体现

    • 线程拿到一把锁后继续申请另一把锁,比如:

      synchronized (lock1) {synchronized (lock2) {// ...}
      }
      
  • 打破策略

    • 一次性申请所有资源
      让线程在执行前先申请所需的所有锁,如果申请失败就释放已拿到的资源,稍后重试。

    • 使用 tryLock + 超时策略

      if (lock1.tryLock(1, TimeUnit.SECONDS)) {try {if (lock2.tryLock(1, TimeUnit.SECONDS)) {try {// 临界区} finally {lock2.unlock();}} else {// 获取 lock2 失败,释放 lock1}} finally {lock1.unlock();}
      }
      

      不等待另一把锁,避免死等。


3. 不可剥夺条件(No Preemption)

  • 解释

    • 线程 已获得的资源,在没使用完之前,不能被系统强制剥夺,只能自己释放。
    • 例如:一个线程占用数据库连接,其他线程只能等它手动释放。
  • Java 中的体现

    • synchronized 和普通 ReentrantLock 都是不可剥夺的。
    • 一旦线程拿到锁,其他线程只能一直等。
  • 打破策略

    • 使用可中断锁
      lockInterruptibly(),如果线程被中断,可以及时退出等待。

      lock.lockInterruptibly();
      
    • 超时锁
      tryLock(timeout),避免无限等待。

    • 强制回滚(在事务/数据库层实现):如果一个线程长期占用资源,其他线程可以中断它,让其释放资源。


4. 循环等待条件(Circular Wait)

  • 解释

    • 存在一个线程等待环,每个线程都在等待下一个线程释放资源。

    • 例如:

      T1: 持有 lockA,等待 lockB
      T2: 持有 lockB,等待 lockA
      
  • Java 中的体现

    • 多线程同时请求多把锁,但获取锁的顺序不同
  • 打破策略

    • 规定固定的锁获取顺序
      所有线程必须按照统一顺序申请锁,例如根据对象 ID 排序:

      Object lockA, lockB;Object first = lockA.hashCode() < lockB.hashCode() ? lockA : lockB;
      Object second = lockA.hashCode() < lockB.hashCode() ? lockB : lockA;synchronized (first) {synchronized (second) {// 临界区}
      }
      

      这样避免了 T1、T2 拿锁顺序不一致导致的环。

    • 资源分级法
      给资源编号,只允许线程按编号递增顺序申请。


如何打破死锁的四个必要条件

要避免死锁,必须破坏至少一个必要条件,具体方法如下:

条件解释打破方法
互斥资源一次只能被一个线程占用使用无锁算法(如 CAS)、读写锁(允许多线程共享读)
占有且等待已占有资源的线程等待其他资源时不释放已有资源一次申请所有资源;使用 tryLock 获取失败时释放已有资源并重试
不可剥夺已获得的资源不能被强制剥夺使用 lockInterruptibly()tryLock(timeout),支持超时退出和中断
循环等待存在线程资源等待环规定统一的资源申请顺序(如按资源 ID 排序),防止线程形成资源等待环

在这里插入图片描述

如何排查死锁

在 Java 中,死锁的排查可以采用以下几种方法:

1. 使用 jstack 工具

  1. 通过 jps -l 获取 Java 进程 PID。
  2. 执行 jstack <PID> 导出线程堆栈信息。
  3. 搜索 Found one Java-level deadlock: 判断是否发生死锁。
    在这里插入图片描述

2. 使用 ThreadMXBean API

可以在程序中引入检测逻辑:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.lang.management.ThreadInfo;public class DeadlockChecker {public static void main(String[] args) {ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();long[] deadlockedThreads = threadBean.findDeadlockedThreads();if (deadlockedThreads != null) {ThreadInfo[] infos = threadBean.getThreadInfo(deadlockedThreads);for (ThreadInfo info : infos) {System.out.println("发现死锁线程: " + info.getThreadName());}} else {System.out.println("未发现死锁");}}
}

3. 使用 IDE 工具

如 IntelliJ IDEA 调试时,可以在 Threads 面板查看线程状态,BLOCKED 的线程相互依赖时可能是死锁。


小结

死锁是多线程编程中的常见问题,理解其产生的四个条件及排查方法是关键。在实际开发中,建议使用超时锁、可中断锁、统一资源申请顺序等技术来避免死锁,从源头上降低风险。


必要条件/充分条件

必要条件(necessary condition)
如果 没有 A,那就一定没有 B。
换句话说:A 不成立 → B 不成立
但:A 成立 ≠ B 一定成立
类比:
空气是人活着的必要条件
没空气 → 人活不了
有空气 ≠ 人一定活着(还需要水、食物等)

充分条件(sufficient condition)
如果 有 A,那就一定有 B。
换句话说:A 成立 → B 一定成立
但:B 成立 ≠ A 一定成立
类比:
“下大雨”是“地面湿” 的充分条件
下大雨 → 地面必湿
地面湿 ≠ 一定是下雨(可能是洒水车路过)

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

相关文章:

  • JavaScript数组方法——梳理和考点
  • 20-C#构造函数--虚方法
  • 深度学习11(调参设参+批标准化)
  • tomcat设置预防host头攻击
  • 使用octomap将pcd点云地图转化为八叉树地图和占据栅格地图
  • MCP快速入门—快速构建自己的服务器
  • 龙虎榜——20250709
  • OpenAI 推出其 AI 代理框架的四项关键更新
  • Python数据分析案例|从模拟数据到可视化:零售门店客流量差异分析全流程
  • 拼多多正在错失即时零售?
  • C++智能指针与Qt内存管理详解
  • RESTful接口设计规范详解
  • SAP采购管理系统替代选谁?8Manage SRM全面优势测评与深度对比
  • 码云创建分支
  • 网络请求与现实生活:用办理业务类比理解HTTP通信
  • ubuntu环境下调试 RT-Thread
  • 降AI工具有哪些推荐?降AI率网站的选择与使用指南
  • 人工智能-基础篇-27-模型上下文协议--MCP到底怎么理解?对比HTTP的区别?
  • SDR(软件定义无线电)与软件定义声学系统详解
  • ECR仓库CloudFormation模板完整指南
  • 第1章 Excel界面环境与基础操作指南
  • 精准医疗,AR 锚定球囊扩张导管为健康护航​
  • 微信小程序控制空调之微信小程序篇
  • 机器学习(西瓜书) 第四章 决策树
  • 【论文阅读】AdaReasoner: Adaptive Reasoning Enables More Flexible Thinking
  • 量化数据接口,level2历史数据,level2实时数据,逐笔成交,逐笔委托,10档行情接口
  • 姿态估计:捕捉人体动作的科技艺术
  • 科技对生态保育的影响?
  • Git系列--3.分支管理
  • 自学软件测试需要学哪些内容?