
本文介绍一种基于 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>> 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() <h4>⚠️ 注意事项与最佳实践</h4>
<ul>
<li>
<strong>永远按固定顺序加锁</strong>:<code>orderAccounts()</code> 确保每次对 <code>acc1</code> 和 <code>acc2</code> 总是先锁 <code>ID</code> 小者,从根本上避免死锁;</li>
<li>
<strong>双重检查(Double-Check)必不可少</strong>:读锁内检查 + 写锁内二次确认,防止 TOCTOU(Time-of-Check-to-Time-of-Use)漏洞;</li>
<li>
<strong>避免 <code>wait()/notify()</code> 手动协调</strong>:它易出错、难调试;用 <code>Future</code> + 定时器替代更可靠;</li>
<li>
<strong>清理逻辑需幂等</strong>:<code>TimerTask</code> 中的 <code>accountsInUse.remove(...)</code> 应容忍重复移除(<code>ConcurrentHashMap</code> 的 <code>remove()</code> 是安全的);</li>
<li>
<strong>异常处理要闭环</strong>:若 <code>submit()</code> 抛异常或 <code>Future.get()</code> 失败,应主动调用 <code>accountsInUse.removeAll(...)</code> 回滚占用状态(示例中未展开,实际项目必须实现)。</li>
</ul>
<h4>✅ 总结</h4>
<p>该方案将“资源占用管理”与“业务执行”解耦:用 <code>ConcurrentHashMap</code> 和读写锁高效管控账户占用状态,用线程池异步执行具体转账,并通过后台定时器自治清理。它不仅彻底消除了原始代码中的竞态条件,还显著提升了系统的吞吐量与可维护性——真正实现了 <strong>“不同账户对转账完全并行,相同账户转账严格串行”</strong> 的设计目标。</p></transactionresult></future></integer>










