Java死锁避免的五种方法举例总结

 更新时间:2025年05月07日 08:33:14   作者:EthanMilk  
这篇文章主要介绍了Java死锁避免的五种方法,包括按固定顺序加锁、使用tryLock+超时、一次性申请所有资源、使用Lock替代synchronized以及检测与恢复,需要的朋友可以参考下

前言

死锁(Deadlock)是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的情况。Java 中可以通过以下方法避免死锁:

造成死锁的⼏个原因:

  • ⼀个资源每次只能被⼀个线程使⽤

  • ⼀个线程在阻塞等待某个资源时,不释放已占有资源

  • ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺

  • 若⼲线程形成头尾相接的循环等待资源关系

这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3 个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。

在开发过程中:

  • 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁

  • 要注意加锁时限,可以针对所设置⼀个超时时间

  • 要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

1. 死锁产生的四个必要条件

要避免死锁,首先要理解死锁发生的条件(必须全部满足):

  • 互斥条件:资源一次只能被一个线程占用。

  • 占有并等待:线程持有资源的同时,等待其他资源。

  • 不可抢占:线程持有的资源不能被其他线程强行夺走。

  • 循环等待:多个线程形成环形等待链(A 等 B,B 等 C,C 等 A)。

只要破坏其中任意一个条件,就能避免死锁!

2. 避免死锁的 5 种方法

方法 1:按固定顺序获取锁(破坏循环等待)

核心思想:所有线程按相同的顺序申请锁,避免循环依赖。

✅ 正确示例

public void transfer(Account from, Account to, int amount) {
    // 规定:先锁 id 小的账户,再锁 id 大的账户
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;

    synchronized (first) {
        synchronized (second) {
            if (from.getBalance() >= amount) {
                from.debit(amount);
                to.credit(amount);
            }
        }
    }
}

为什么能避免死锁?所有线程都按 id 顺序加锁,不会出现 A 锁 1 → 等 2 和 B 锁 2 → 等 1 的情况。

方法 2:使用 tryLock + 超时(破坏占有并等待)

核心思想:如果拿不到锁,就释放已持有的锁,避免无限等待。

✅ 正确示例(ReentrantLock)

public boolean transfer(Account from, Account to, int amount, long timeout) throws InterruptedException {
    long startTime = System.currentTimeMillis();
    while (true) {
        if (from.lock.tryLock()) { // 尝试获取第一把锁
            try {
                if (to.lock.tryLock()) { // 尝试获取第二把锁
                    try {
                        if (from.getBalance() >= amount) {
                            from.debit(amount);
                            to.credit(amount);
                            return true;
                        }
                    } finally {
                        to.lock.unlock();
                    }
                }
            } finally {
                from.lock.unlock(); // 释放第一把锁
            }
        }
        // 超时检查
        if (System.currentTimeMillis() - startTime >= timeout) {
            return false;
        }
        Thread.sleep(100); // 避免 CPU 忙等待
    }
}

优点

  • 不会无限等待,超时后可以重试或回滚。

  • 适用于高并发场景(如支付系统)。

方法 3:一次性申请所有资源(破坏占有并等待)

核心思想:使用一个全局锁,一次性申请所有需要的资源。

✅ 正确示例

public class AccountManager {
    private static final Object globalLock = new Object();

    public void transfer(Account from, Account to, int amount) {
        synchronized (globalLock) { // 全局锁保护所有账户
            if (from.getBalance() >= amount) {
                from.debit(amount);
                to.credit(amount);
            }
        }
    }
}

缺点:并发度降低(所有转账操作串行化)。

方法 4:使用 Lock 替代 synchronized(更灵活的控制)

ReentrantLock 比 synchronized 更灵活,可以避免死锁:

Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();

public void methodA() {
    boolean gotLock1 = false;
    boolean gotLock2 = false;
    try {
        gotLock1 = lock1.tryLock(1, TimeUnit.SECONDS);
        gotLock2 = lock2.tryLock(1, TimeUnit.SECONDS);
        if (gotLock1 && gotLock2) {
            // 执行业务逻辑
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (gotLock1) lock1.unlock();
        if (gotLock2) lock2.unlock();
    }
}

方法 5:检测与恢复(允许死锁发生,但能自动恢复)

适用于复杂系统(如数据库、分布式系统):

1.检测死锁

使用 ThreadMXBean 查找死锁:

ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
    System.out.println("检测到死锁!");
}

2.恢复策略

强制终止某个线程(如 thread.interrupt())。

回滚事务(数据库场景)。

3. 死锁案例分析

❌ 错误代码(会导致死锁)

public void transfer(Account from, Account to, int amount) {
    synchronized (from) {  // 线程1:锁 from → 等 to
        synchronized (to) { // 线程2:锁 to → 等 from
            if (from.getBalance() >= amount) {
                from.debit(amount);
                to.credit(amount);
            }
        }
    }
}

死锁场景

  • 线程1:transfer(accountA, accountB, 100)

  • 线程2:transfer(accountB, accountA, 200)

  • 结果:互相等待,死锁!

4. 总结

方法适用场景优点缺点
固定顺序加锁简单锁依赖实现简单需要全局排序规则
tryLock + 超时高并发系统避免无限等待代码复杂度高
全局锁低并发场景绝对安全性能差(串行化)
Lock 替代 synchronized需要更细粒度控制灵活(可中断、超时)需手动释放锁
检测与恢复数据库、分布式系统适用于复杂场景实现复杂

最佳实践

  • 尽量用 tryLock + 超时(推荐 ReentrantLock)。

  • 如果必须用 synchronized,按固定顺序加锁

  • 避免嵌套锁(如 synchronized 里再调 synchronized 方法)。

  • 使用工具检测死锁(如 jstackThreadMXBean)。

到此这篇关于Java死锁避免的五种方法的文章就介绍到这了,更多相关Java死锁避免内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何使用java判断是不是数字

    如何使用java判断是不是数字

    这篇文章主要给大家介绍了关于如何使用java判断是不是数字的相关资料,判断一个字符串是否为数字是Java开发中很常见的业务需求,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • Java线程池 ThreadPoolExecutor 详解

    Java线程池 ThreadPoolExecutor 详解

    这篇文章主要介绍了Java线程池 ThreadPoolExecutor,线程池包括线程集合、阻塞队列、拒绝策略处理器,更多相关内容需要的朋友可以参考一下
    2022-07-07
  • SpringBoot 项目中的图片处理策略之本地存储与路径映射

    SpringBoot 项目中的图片处理策略之本地存储与路径映射

    在SpringBoot项目中,静态资源存放在static目录下,使得前端可以通过URL来访问这些资源,我们就需要将文件系统的文件路径与URL建立一个映射关系,把文件系统中的文件当成我们的静态资源即可,本文给大家介绍SpringBoot本地存储与路径映射的相关知识,感兴趣的朋友一起看看吧
    2023-12-12
  • 浅析SpringBoot自动装配的实现

    浅析SpringBoot自动装配的实现

    springboot开箱即用,其实实现了自动装配,本文重点给大家介绍SpringBoot是如何做到自动装配的,感兴趣的朋友跟随小编一起看看吧
    2022-02-02
  • 关于二分法查找Java的实现及解析

    关于二分法查找Java的实现及解析

    这篇文章主要介绍了关于二分法查找Java的实现及解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Java图像处理教程之正片叠底效果的实现

    Java图像处理教程之正片叠底效果的实现

    正片叠底效果是我们平时在Photoshop中会见到的一种效果,下面这篇文章主要给大家介绍了关于利用Java如何实现正片叠底的效果,分享出来供大家参考学习,文中给出了详细的示例代码供大家参考学习,需要的朋友可以参考借鉴,下面来一起看看详细的介绍吧。
    2017-09-09
  • Java导出txt文件的方法

    Java导出txt文件的方法

    这篇文章主要介绍了Java导出txt文件的方法,实例分析了两种java导出txt文本文件的使用技巧,需要的朋友可以参考下
    2015-05-05
  • 解决springboot 获取form-data里的file文件的问题

    解决springboot 获取form-data里的file文件的问题

    这篇文章主要介绍了解决springboot 获取form-data里的file文件的问题的相关资料,这里提供了详细的解决步骤,需要的朋友可以参考下
    2017-07-07
  • 使用springMVC通过Filter实现防止xss注入

    使用springMVC通过Filter实现防止xss注入

    这篇文章主要介绍了使用springMVC通过Filter实现防止xss注入的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Springboot项目的搭建教程(分离出common父依赖)

    Springboot项目的搭建教程(分离出common父依赖)

    这篇文章主要介绍了Springboot项目的搭建教程(分离出common父依赖),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论