死锁的产生以及如何避免

死锁的产生与避免

一、死锁的产生原因

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

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

六、总结

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

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

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

相关推荐
Peter(阿斯拉)4 分钟前
[Java性能优化]_[时间优化]_[字符串拼接的多种方法性能分析]
java·性能优化·stringbuilder·string·字符串拼接·stringbuffer·时间优化
水痕011 小时前
gin结合minio来做文件存储
java·eureka·gin
寒士obj2 小时前
Spring事物
java·spring
柯南二号3 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
桦说编程10 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
lifallen10 小时前
Java Stream sort算子实现:SortedOps
java·开发语言
IT毕设实战小研10 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
没有bug.的程序员11 小时前
JVM 总览与运行原理:深入Java虚拟机的核心引擎
java·jvm·python·虚拟机
甄超锋11 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国11 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端