You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免同步方法死锁?以转账方法交叉调用场景为例

按对象参数分组同步的转账方法与死锁解决

需求明确

我们需要实现transferMoney方法,满足两个核心要求:

  • 当两次调用的账户参数完全不重叠时(比如调用(a,b)和(c,d)),方法可以并行执行,不互相阻塞
  • 当两次调用的账户参数有重叠时(比如(a,b)和(a,c),或是(a,b)和(b,a)),方法必须串行执行,同时要避免(a,b)和(b,a)交叉调用时出现死锁

可行解决方案

1. 固定锁的获取顺序(最经典的死锁解决方法)

给每个Account分配一个唯一的标识(比如自增ID),获取锁时总是先获取ID较小的账户的锁,再获取ID较大的。这样不管调用顺序是(a,b)还是(b,a),所有线程都会遵循相同的锁获取顺序,从根源上避免死锁。

代码实现:

public void transferMoney(Account a, Account b, float value) {
    // 确定锁的获取顺序,保证(a,b)和(b,a)的锁顺序一致
    Account firstLock = a.getId() < b.getId() ? a : b;
    Account secondLock = a.getId() < b.getId() ? b : a;

    synchronized(firstLock) {
        synchronized(secondLock) {
            // 执行具体转账逻辑
            doTransfer(a, b, value);
        }
    }
}

// 示例Account类,包含唯一ID和余额
class Account {
    private final int id;
    private float balance;

    public Account(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    // 转账核心逻辑,需保证线程安全(这里已被外层锁保护)
    private void doTransfer(Account from, Account to, float value) {
        from.balance -= value;
        to.balance += value;
    }
}

优点:实现简单,无额外内存开销,并发度高,彻底避免死锁;完全符合“仅同一对象参与时同步”的需求——只要账户有重叠,锁的串行获取会自动保证同一账户的操作不会并发执行。

2. 基于一致哈希的分组同步

通过生成与账户顺序无关的哈希键,让(a,b)和(b,a)映射到同一个锁对象,从而保证交叉调用使用同一把锁,避免死锁。需要注意处理哈希冲突的问题。

代码实现:

import java.util.concurrent.ConcurrentHashMap;

public class TransferService {
    // 全局锁映射表,存储哈希键对应的锁对象
    private static final ConcurrentHashMap<Long, Object> lockMap = new ConcurrentHashMap<>();

    public void transferMoney(Account a, Account b, float value) {
        // 生成与账户顺序无关的哈希键
        long hashKey = generateConsistentHash(a, b);
        // 获取或创建对应的锁对象
        Object lock = lockMap.computeIfAbsent(hashKey, k -> new Object());

        synchronized(lock) {
            // 内层仍需对账户加锁,防止其他未通过该方法的并发操作
            synchronized(a) {
                synchronized(b) {
                    doTransfer(a, b, value);
                }
            }
        }

        // 可选:移除无用锁对象,避免内存泄漏
        lockMap.remove(hashKey, lock);
    }

    // 生成(a,b)和(b,a)一致的哈希值
    private long generateConsistentHash(Account a, Account b) {
        long hashA = System.identityHashCode(a);
        long hashB = System.identityHashCode(b);
        // 保证顺序不影响结果:小哈希值左移32位拼接大哈希值
        return hashA < hashB ? (hashA << 32) | hashB : (hashB << 32) | hashA;
    }
}

优点:不需要给Account增加ID字段;缺点:存在哈希冲突风险(不同账户对可能生成相同哈希键),会导致无关转账被串行,降低并发度;同时需要维护全局锁映射表,注意内存泄漏问题。

3. 使用显式锁+超时重试

通过ReentrantLock的tryLock方法尝试获取锁,若获取失败则释放已获取的锁,短暂休眠后重试,直到获取所有锁或超时。这种方式不会出现永久死锁,适合无法固定锁顺序的场景。

代码实现:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public void transferMoney(Account a, Account b, float value) throws InterruptedException {
    long timeout = 1000; // 超时时间1秒
    long endTime = System.currentTimeMillis() + timeout;

    Lock lockA = a.getLock();
    Lock lockB = b.getLock();

    while (true) {
        boolean gotLockA = lockA.tryLock();
        try {
            boolean gotLockB = lockB.tryLock();
            try {
                if (gotLockA && gotLockB) {
                    doTransfer(a, b, value);
                    return;
                }
            } finally {
                if (gotLockB) lockB.unlock();
            }
        } finally {
            if (gotLockA) lockA.unlock();
        }

        // 超时则抛出异常或返回失败
        if (System.currentTimeMillis() > endTime) {
            throw new RuntimeException("转账超时,避免死锁");
        }

        // 短暂休眠,避免忙等
        Thread.sleep(10);
    }
}

// Account类持有自己的显式锁
class Account {
    private final Lock lock = new ReentrantLock();
    private float balance;

    public Lock getLock() {
        return lock;
    }
}

优点:无需修改Account结构,不会出现永久死锁;缺点:需要处理重试和超时逻辑,高并发下重试会增加CPU开销,且可能存在转账失败的情况。

对您思路的分析

  • volatile修饰a和b:无意义。volatile仅保证变量的可见性,而方法参数a、b是线程私有的,且volatile无法解决同步和死锁问题。
  • 包含a和b的新对象:如果每次调用都创建新对象,每个调用的锁都不同,完全起不到同步作用;若要实现同一参数组合同步,必须像哈希方案那样做一致的映射,否则新对象无法满足需求。

内容的提问来源于stack exchange,提问作者Stupidquestion010

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 05:10:31