基于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
相关产品推荐
相关产品推荐

