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

多线程银行项目中synchronized块的竞态条件解决咨询

多线程银行转账竞态条件解决方案建议

问题背景

开发带多线程功能的简易银行项目,核心操作包含transfer和transfer_i(交互式账户转账会话)。当前transfer和transfer_iInitiate方法中标注//race condition的位置存在竞态条件,transfer_iInitiate会将会话涉及账户存入ArrayList transfer_i。要求:

  • 禁止使用方法签名synchronized或synchronized(this)解决竞态问题
  • 支持不同线程同时执行无重叠账户的transfer_i操作(如a→b和c→d,四个账户互不相同)

之前尝试的方案:

synchronized(acc1){
  synchronized(acc2){
  // 此处包含while循环和transfer_i.add(...)操作
  }
}

遇到的问题:无法在while循环中连续对acc1和acc2调用wait。


可行解决方案

1. 统一账户锁的获取顺序

先解决死锁隐患,同时为后续wait操作铺路:
给每个账户分配唯一标识(如账户名哈希值、唯一ID),获取锁时始终先锁定标识较小的账户,再锁定标识较大的。这种方式能避免交叉锁导致的死锁,同时让逻辑能在统一的锁块内执行。

2. 用Condition替代直接调用wait

不要直接对账户对象调用wait,给每个账户关联基于ReentrantLock的Condition,实现更灵活的等待与唤醒控制:

// 为每个账户维护独立的锁和条件变量
private final Map<String, ReentrantLock> accountLocks = new ConcurrentHashMap<>();
private final Map<String, Condition> accountConditions = new ConcurrentHashMap<>();

// 初始化账户时创建对应锁和条件
public void initAccount(String accName) {
    accountLocks.putIfAbsent(accName, new ReentrantLock());
    accountConditions.putIfAbsent(accName, accountLocks.get(accName).newCondition());
}

// 优化后的transfer_iInitiate逻辑
public void transfer_iInitiate(String accFrom, String accTo) throws InterruptedException {
    ReentrantLock lockFrom = accountLocks.get(accFrom);
    ReentrantLock lockTo = accountLocks.get(accTo);
    
    // 按固定顺序获取锁,避免死锁
    if (accFrom.hashCode() < accTo.hashCode()) {
        lockFrom.lock();
        try {
            lockTo.lock();
            try {
                // 循环检查转账条件(如余额是否充足)
                while (!canTransfer(accFrom, accTo)) {
                    // 等待账户条件满足(比如余额变动)
                    accountConditions.get(accFrom).await();
                }
                // 执行转账操作
                executeTransfer(accFrom, accTo);
                // 安全添加账户到transfer_i列表(见第3点)
                safeAddToTransferList(accFrom, accTo);
                // 唤醒等待该账户的其他线程
                accountConditions.get(accFrom).signalAll();
                accountConditions.get(accTo).signalAll();
            } finally {
                lockTo.unlock();
            }
        } finally {
            lockFrom.unlock();
        }
    } else {
        // 反向获取锁,保证顺序一致
        lockTo.lock();
        try {
            lockFrom.lock();
            try {
                while (!canTransfer(accFrom, accTo)) {
                    accountConditions.get(accFrom).await();
                }
                executeTransfer(accFrom, accTo);
                safeAddToTransferList(accFrom, accTo);
                accountConditions.get(accFrom).signalAll();
                accountConditions.get(accTo).signalAll();
            } finally {
                lockFrom.unlock();
            }
        } finally {
            lockTo.unlock();
        }
    }
}

3. 保障transfer_i列表的线程安全

ArrayList并非线程安全容器,针对添加操作需额外处理:

  • 替换为CopyOnWriteArrayList,原生支持线程安全的添加操作
  • 或用专属锁同步添加逻辑:
private final Object listLock = new Object();
private List<String> transfer_i = new ArrayList<>();

private void safeAddToTransferList(String... accounts) {
    synchronized(listLock) {
        for (String acc : accounts) {
            transfer_i.add(acc);
        }
    }
}

4. 拆分多账户wait逻辑

若需同时等待两个账户的条件,不要连续调用wait,而是在循环中分别检查并等待:

while (!checkAccCondition(accFrom) || !checkAccCondition(accTo)) {
    if (!checkAccCondition(accFrom)) {
        accountConditions.get(accFrom).await();
    } else {
        accountConditions.get(accTo).await();
    }
}

这种方式能避免因连续wait导致的锁持有问题,同时保证每次唤醒后重新检查所有条件。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:55:40