死锁的产生以及如何避免

死锁的产生与避免

一、死锁的产生原因

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

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

六、总结

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

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

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

相关推荐
夏天的味道٥3 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
冰糖码奇朵5 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好5 小时前
【Spring】整合【SpringMVC】
java·spring
浪九天6 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代6 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n6 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven
香精煎鱼香翅捞饭7 小时前
java通用自研接口限流组件
java·开发语言
ChinaRainbowSea7 小时前
Linux: Centos7 Cannot find a valid baseurl for repo: base/7/x86_64 解决方案
java·linux·运维·服务器·docker·架构
囧囧 O_o7 小时前
Java 实现 Oracle 的 MONTHS_BETWEEN 函数
java·oracle
去看日出7 小时前
RabbitMQ消息队列中间件安装部署教程(Windows)-2025最新版详细图文教程(附所需安装包)
java·windows·中间件·消息队列·rabbitmq