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

基于Java CAS与AtomicReference的转账代码是否线程安全?

多线程转账非阻塞方案的并发问题排查与修复

问题根源

你的方案出现总余额不一致的核心原因是转账操作缺乏原子性:从from账户扣款和向to账户加款是两个独立的操作,没有被封装为一个原子事务。极端场景下(比如扣款成功后加款操作因线程中断、CAS重试异常终止),会导致钱“丢失”,总余额减少。此外还有两个次要问题:

  • 校验与实际操作存在时间窗口:transfer方法的余额校验和后续withdraw并非原子执行,虽不会直接导致余额不一致,但会引发不必要的异常。
  • 单账户锁无法保障跨账户原子性:单个账户的锁只能保证自身操作安全,无法协调两个账户间的转账流程。

修复方案:非阻塞原子转账+重试+锁降级

将整个转账流程(余额校验、扣款、加款)封装为可重试的原子操作,双CAS失败时执行回滚,多次重试后降级为有序加锁的阻塞方案,确保最终一致性。

1. 调整AtomicRefAccount类

放宽必要成员的访问权限,或提供公开的CAS方法,方便跨账户操作:

public class AtomicRefAccount implements Account {
    private final int id;
    // 放宽访问权限,或提供getter方法
    final AtomicReference<BigDecimal> atomicAmount;
    private final int MAX_RETRY = 5;
    final Lock lock = new ReentrantLock(); // 用于锁降级时的跨账户加锁

    public AtomicRefAccount(AtomicReference<BigDecimal> atomicAmount, int id) {
        this.id = id;
        this.atomicAmount = atomicAmount;
    }

    public static AtomicRefAccount of(int amount, int id) {
        return new AtomicRefAccount(new AtomicReference<>(new BigDecimal(amount)), id);
    }

    // 提供公开的CAS方法,避免直接暴露atomicAmount
    public boolean compareAndSetBalance(BigDecimal expect, BigDecimal update) {
        return atomicAmount.compareAndSet(expect, update);
    }

    // 其余原有方法保持不变...
}

2. 重写transfer方法实现原子转账

public class DefaultMoneyTransferService implements MoneyTransferService {
    private static final int MAX_TRANSFER_RETRY = 10;

    @Override
    public void transfer(Account from, Account to, BigDecimal amountToTransfer) {
        // 参数校验
        Objects.requireNonNull(from, "[from] account is null");
        Objects.requireNonNull(to, "[to] account is null");
        Objects.requireNonNull(amountToTransfer, "[amountToTransfer] is null");
        if (from == to) {
            throw new IllegalArgumentException("[from|to] are the same account");
        }
        if (amountToTransfer.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("[amountToTransfer] must be positive");
        }

        AtomicRefAccount atomicFrom = (AtomicRefAccount) from;
        AtomicRefAccount atomicTo = (AtomicRefAccount) to;

        // 非阻塞重试阶段
        int retryCount = 0;
        while (retryCount < MAX_TRANSFER_RETRY) {
            BigDecimal fromBalance = atomicFrom.getBalance();
            BigDecimal toBalance = atomicTo.getBalance();

            // 检查余额是否足够
            if (fromBalance.compareTo(amountToTransfer) < 0) {
                throw new IllegalStateException("[from] insufficient funds");
            }

            // 第一步:尝试扣减from账户
            boolean fromUpdated = atomicFrom.compareAndSetBalance(fromBalance, fromBalance.subtract(amountToTransfer));
            if (fromUpdated) {
                try {
                    // 第二步:尝试增加to账户
                    boolean toUpdated = atomicTo.compareAndSetBalance(toBalance, toBalance.add(amountToTransfer));
                    if (toUpdated) {
                        return; // 转账成功
                    } else {
                        // to账户更新失败,回滚from账户
                        atomicFrom.compareAndSetBalance(fromBalance.subtract(amountToTransfer), fromBalance);
                    }
                } catch (Throwable e) {
                    // 发生异常时回滚from账户
                    atomicFrom.compareAndSetBalance(fromBalance.subtract(amountToTransfer), fromBalance);
                    throw e;
                }
            }
            retryCount++;
            Thread.yield(); // 让出CPU,减少自旋消耗
        }

        // 重试失败,降级为阻塞锁方案(按账户ID顺序加锁避免死锁)
        Lock firstLock = atomicFrom.getId() < atomicTo.getId() ? atomicFrom.lock : atomicTo.lock;
        Lock secondLock = atomicFrom.getId() < atomicTo.getId() ? atomicTo.lock : atomicFrom.lock;
        firstLock.lock();
        secondLock.lock();
        try {
            BigDecimal fromBalance = atomicFrom.getBalance();
            if (fromBalance.compareTo(amountToTransfer) < 0) {
                throw new IllegalStateException("[from] insufficient funds");
            }
            // 已加锁,直接更新即可
            atomicFrom.atomicAmount.set(fromBalance.subtract(amountToTransfer));
            atomicTo.atomicAmount.set(atomicTo.getBalance().add(amountToTransfer));
        } finally {
            secondLock.unlock();
            firstLock.unlock();
        }
    }
}

关键改进点

  • 原子性保障:通过双CAS操作+回滚机制,确保转账要么完全成功,要么完全回滚,避免部分执行导致的余额不一致。
  • 死锁避免:锁降级阶段按账户ID顺序加锁,彻底消除死锁风险。
  • 异常安全:加款失败或发生异常时,自动回滚已完成的扣款操作,保证数据一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:29:54