死锁的产生以及如何避免

死锁的产生与避免

一、死锁的产生原因

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

  1. 互斥条件(Mutual Exclusion)

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

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

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

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

二、典型死锁场景示例

Java 代码示例

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自带工具):

    bash 复制代码
    jstack <pid>  # 输出线程快照,显示死锁的线程及持有/等待的锁
  • VisualVMJConsole
    图形化界面查看线程状态,直接标记死锁。

2. 日志分析

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


四、死锁的避免策略

1. 破坏"请求与保持"条件

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

2. 破坏"不可剥夺"条件

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

3. 破坏"循环等待"条件

  • 资源有序分配法

    为所有资源类型定义全局顺序,线程按顺序申请资源。
    示例

    规定必须先申请 lockA 再申请 lockB,避免交叉申请。

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

4. 使用超时机制

  • 尝试获取锁时设置超时
    若在指定时间内未获得锁,放弃并释放已持有的资源,避免无限等待。
    Java实现 (使用 ReentrantLock):

    java 复制代码
    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、原子类) 低竞争、简单操作场景 高性能,无死锁风险 复杂逻辑实现困难
事务回滚 数据库、支持回滚的操作 保证数据一致性 实现成本高

六、总结

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

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

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

相关推荐
我命由我123452 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
武子康4 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
YuTaoShao7 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw7 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨7 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
双力臂4048 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
Edingbrugh.南空8 小时前
Aerospike与Redis深度对比:从架构到性能的全方位解析
java·开发语言·spring
QQ_4376643149 小时前
C++11 右值引用 Lambda 表达式
java·开发语言·c++
永卿0019 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式
誰能久伴不乏9 小时前
Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
java·服务器·前端