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

死锁的产生以及如何避免

死锁的产生与避免

    • 一、死锁的产生原因
    • 二、典型死锁场景示例
    • 三、死锁的检测与诊断
      • 1. 使用工具检测死锁
      • 2. 日志分析
    • 四、死锁的避免策略
      • 1. 破坏“请求与保持”条件
      • 2. 破坏“不可剥夺”条件
      • 3. 破坏“循环等待”条件
      • 4. 使用超时机制
      • 5. 减少锁的粒度
    • 五、最佳实践总结
    • 六、总结

一、死锁的产生原因

死锁是多个线程(或进程)因竞争资源而陷入无限等待的状态,需同时满足以下 四个必要条件

  1. 互斥条件(Mutual Exclusion)

    • 资源一次只能被一个线程独占使用(如锁、文件句柄)。
  2. 请求与保持(Hold and Wait)

    • 线程在持有至少一个资源的同时,请求其他线程占有的资源。
  3. 不可剥夺(No Preemption)

    • 资源只能由持有者主动释放,不能被强制抢占。
  4. 循环等待(Circular Wait)

    • 多个线程形成环形等待链,每个线程都在等待下一个线程释放资源。

二、典型死锁场景示例

Java 代码示例

public class DeadlockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println("Thread1 holds lockA");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockB) {  // 等待Thread2释放lockB
                    System.out.println("Thread1 holds lockB");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lockB) {
                System.out.println("Thread2 holds lockB");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockA) {  // 等待Thread1释放lockA
                    System.out.println("Thread2 holds lockA");
                }
            }
        }).start();
    }
}

结果
两个线程互相等待对方释放锁,程序无限卡死。


三、死锁的检测与诊断

1. 使用工具检测死锁

  • jstack(Java自带工具):
    jstack <pid>  # 输出线程快照,显示死锁的线程及持有/等待的锁
    
  • VisualVMJConsole
    图形化界面查看线程状态,直接标记死锁。

2. 日志分析

若日志中线程长时间处于 BLOCKED 状态且无进展,可能发生死锁。


四、死锁的避免策略

1. 破坏“请求与保持”条件

  • 一次性申请所有资源
    线程在开始执行前申请全部所需资源,否则不执行。
    缺点:资源利用率低,可能导致饥饿。

2. 破坏“不可剥夺”条件

  • 允许抢占资源
    若线程请求资源失败,强制释放已持有的资源(需支持回滚操作)。
    缺点:实现复杂,适用于特定场景(如数据库事务)。

3. 破坏“循环等待”条件

  • 资源有序分配法
    为所有资源类型定义全局顺序,线程按顺序申请资源。
    示例
    规定必须先申请 lockA 再申请 lockB,避免交叉申请。

    // 修改后代码:两个线程均按 lockA → lockB 顺序申请
    new Thread(() -> {
        synchronized (lockA) {
            synchronized (lockB) { /* 逻辑 */ }
        }
    }).start();
    
    new Thread(() -> {
        synchronized (lockA) {
            synchronized (lockB) { /* 逻辑 */ }
        }
    }).start();
    

4. 使用超时机制

  • 尝试获取锁时设置超时
    若在指定时间内未获得锁,放弃并释放已持有的资源,避免无限等待。
    Java实现(使用 ReentrantLock):
    Lock lockA = new ReentrantLock();
    Lock lockB = new ReentrantLock();
    
    if (lockA.tryLock(1, TimeUnit.SECONDS)) {
        try {
            if (lockB.tryLock(1, TimeUnit.SECONDS)) {
                try { /* 逻辑 */ } 
                finally { lockB.unlock(); }
            }
        } finally { lockA.unlock(); }
    }
    

5. 减少锁的粒度

  • 缩小同步范围
    仅对必要代码加锁,减少锁的持有时间。
  • 使用线程安全的数据结构
    ConcurrentHashMap 替代 synchronized + HashMap

五、最佳实践总结

策略适用场景优点缺点
资源有序分配多锁交叉申请场景简单有效,预防循环等待需全局统一顺序,可能限制灵活性
超时机制高并发、允许重试的场景避免无限等待,提升系统健壮性需处理超时重试逻辑
无锁编程(CAS、原子类)低竞争、简单操作场景高性能,无死锁风险复杂逻辑实现困难
事务回滚数据库、支持回滚的操作保证数据一致性实现成本高

六、总结

死锁的避免需结合业务场景选择合适的策略:

  • 关键系统(如金融交易):优先使用资源有序分配和超时机制。
  • 高并发系统:减少锁粒度,采用无锁数据结构。
  • 复杂事务:结合事务管理和回滚机制。

通过代码规范、工具检测和设计优化,可显著降低死锁发生概率。


http://www.kler.cn/a/579849.html

相关文章:

  • PAT乙级(1091 N-自守数)C语言解析
  • 日期类、Date、Calendar、IO 流、File
  • Windows简易操作(二)
  • Science Advances 多功能粘性皮肤增强了机器人与环境的交互
  • JavaScript网页设计案例:打造动态与交互性并存的用户体验
  • 【3DMAX插件】3DMAX建筑大师插件MasterBuilder使用方法
  • Rabbitmq--延迟消息
  • 深入理解C语言预处理器:从原理到实战
  • 游戏引擎学习第148天
  • 使用Python和p5.js创建的迷你游戏示例,该游戏包含多个屏幕和动画,满足在画布上显示图像、使用键盘命令移动图像
  • AXI接口总结
  • DeepSeek-进阶版部署(Linux+GPU)
  • RAG助力机器人场景理解与具身操作!EmbodiedRAG:基于动态三维场景图检索的机器人任务规划
  • 腾讯云低代码开发应用
  • Unity大型游戏开发全流程指南
  • ChātGPT开发“SolidWorks工具箱”,可建海量3D模型库,能一键画图、批量赋属性、自动出图,效率提高10倍
  • JAVA学习-练习试用Java实现“使用FP-Growth算法对大数据集中的频繁模式进行挖掘和筛选”
  • 将Exce中工作簿的多个工作表拆分为单独的Excel文件
  • 鸿蒙“一码多端”:万物互联时代的中国式创新与温度
  • 城市消防无人机系统解决技术详解