多线程银行项目中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
相关产品推荐
相关产品推荐

