
本文介绍一种基于 ConcurrentHashMap、ReentrantReadWriteLock 和线程池的现代 Java 并发方案,用于彻底消除 transfer() 与 transfer_iInitiate() 方法中的竞态条件,同时支持不同账户对之间的并行交互式转账。
本文介绍一种基于 `concurrenthashmap`、`reentrantreadwritelock` 和线程池的现代 java 并发方案,用于彻底消除 `transfer()` 与 `transfer_iinitiate()` 方法中的竞态条件,同时支持不同账户对之间的并行交互式转账。
在多线程银行系统中,竞态条件(Race Condition)常出现在多个线程并发访问共享状态但缺乏一致同步机制时。原代码中两个关键竞态点尤为典型:
- 在
transfer_iInitiate()中:先调用isTransfer_i(g1) || isTransfer_i(g2)检查账户是否已被占用,再执行transfer_i.add(g1); transfer_i.add(g2);—— 这两步之间存在时间窗口,导致两个线程可能同时通过检查并重复添加同一账户; - 在
transfer()中:while (isTransfer_i(g1) || isTransfer_i(g2)) { wait(); }与后续transaction(...)之间无原子性保护,且wait()调用在this上,而notifyAll()又分散在transfer_iEnd()中,极易引发唤醒丢失或死锁。
根本问题在于:使用 ArrayList + synchronized(this) 或嵌套账户锁,既无法细粒度控制资源争用,又难以实现“检查-加锁-操作”三步原子化。
✅ 推荐解决方案:分离关注点 + 无阻塞协调
我们摒弃对 this 或账户实例的粗粒度锁,转而采用以下组合策略:
| 组件 | 作用 | 优势 |
|---|---|---|
ConcurrentHashMap.newKeySet() |
替代 ArrayList<account> transfer_i</account>,线程安全地记录当前被占用的账户 ID 集合
|
无需显式同步即可完成 contains() 和 add() 的原子判断与更新 |
ReentrantReadWriteLock |
控制对 accountsInUse 的读写互斥 |
允许多个线程并发读取占用状态(如检查冲突),仅在修改时独占写入 |
ExecutorService + Future
|
将转账逻辑封装为异步任务提交执行 | 解耦业务逻辑与并发控制,天然支持超时、取消与结果回调 |
后台 TimerTask 定期清理 |
扫描已完成的 Future,自动释放对应账户 ID |
避免手动 notify/wait 的复杂状态管理,提升健壮性 |
? 核心改造步骤(精简可运行版)
// 1. 声明并发安全组件(Bank 类成员)
private final Set<Integer> accountsInUse = ConcurrentHashMap.newKeySet();
private final Set<Future<TransactionResult>> transactions = ConcurrentHashMap.newKeySet();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock reader = lock.readLock();
private final ReentrantReadWriteLock.WriteLock writer = lock.writeLock();
private final ExecutorService executor = Executors.newFixedThreadPool(5);
// 2. 定义事务结果与任务类
record TransactionResult(String status, int acc1Id, int acc2Id) {}
static class TransactionTask implements Callable<TransactionResult> {
private final Account acc1, acc2;
private final double amount;
TransactionTask(Account acc1, Account acc2, double amount) {
this.acc1 = acc1; this.acc2 = acc2; this.amount = amount;
}
@Override
public TransactionResult call() throws Exception {
if (acc1.getBalance() < amount || amount < 0) {
throw new InvalidAmountException("Invalid amount or insufficient balance");
}
// 关键:按 ID 排序加锁,彻底杜绝死锁
Account[] ordered = orderAccounts(acc1, acc2);
synchronized (ordered[0]) {
synchronized (ordered[1]) {
acc1.updateBalance(-amount);
acc2.updateBalance(amount);
acc1.setLastTransaction(new Transaction(-amount, acc1, acc2));
acc2.setLastTransaction(new Transaction(amount, acc1, acc2));
}
}
return new TransactionResult("SUCCESS", acc1.getID(), acc2.getID());
}
}
// 3. 安全的 transfer() 实现(无 wait/notify,无竞态)
public void transfer(String acc1Name, String acc2Name, double amount)
throws AccountNotExists, InvalidAmountException, SafeIllegalArgumentException {
if (acc1Name.equals(acc2Name)) {
throw new SafeIllegalArgumentException("Cannot transfer to self");
}
Account acc1 = getAccount(acc1Name);
Account acc2 = getAccount(acc2Name);
// 原子性检查:是否任一账户已被占用?
reader.lock();
try {
if (accountsInUse.contains(acc1.getID()) || accountsInUse.contains(acc2.getID())) {
throw new AccountNotAvailable("One or both accounts are currently in use");
}
} finally {
reader.unlock();
}
// 原子性登记:抢占账户
writer.lock();
try {
// 再次检查(防止检查后瞬间被其他线程抢占)
if (accountsInUse.contains(acc1.getID()) || accountsInUse.contains(acc2.getID())) {
throw new AccountNotAvailable("Account conflict detected during acquisition");
}
accountsInUse.add(acc1.getID());
accountsInUse.add(acc2.getID());
} finally {
writer.unlock();
}
// 异步执行转账(失败时需回滚 accountsInUse,此处简化;生产环境建议用 CompletableFuture.handle)
transactions.add(executor.submit(new TransactionTask(acc1, acc2, amount)));
}⚠️ 注意事项与最佳实践
-
永远按固定顺序加锁:
orderAccounts()确保每次对acc1和acc2总是先锁ID小者,从根本上避免死锁; - 双重检查(Double-Check)必不可少:读锁内检查 + 写锁内二次确认,防止 TOCTOU(Time-of-Check-to-Time-of-Use)漏洞;
-
避免
wait()/notify()手动协调:它易出错、难调试;用Future+ 定时器替代更可靠; -
清理逻辑需幂等:
TimerTask中的accountsInUse.remove(...)应容忍重复移除(ConcurrentHashMap的remove()是安全的); -
异常处理要闭环:若
submit()抛异常或Future.get()失败,应主动调用accountsInUse.removeAll(...)回滚占用状态(示例中未展开,实际项目必须实现)。
✅ 总结
该方案将“资源占用管理”与“业务执行”解耦:用 ConcurrentHashMap 和读写锁高效管控账户占用状态,用线程池异步执行具体转账,并通过后台定时器自治清理。它不仅彻底消除了原始代码中的竞态条件,还显著提升了系统的吞吐量与可维护性——真正实现了 “不同账户对转账完全并行,相同账户转账严格串行” 的设计目标。

















