java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java死锁避免

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

作者:EthanMilk

这篇文章主要介绍了Java死锁避免的五种方法,包括按固定顺序加锁、使用tryLock+超时、一次性申请所有资源、使用Lock替代synchronized以及检测与恢复,需要的朋友可以参考下

前言

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

造成死锁的⼏个原因:

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

在开发过程中:

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

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

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

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);
            }
        }
    }
}

死锁场景

4. 总结

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

最佳实践

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

您可能感兴趣的文章:
阅读全文