多线程环境下线程安全方案正确性验证及其他实现思路咨询
多线程环境下交易处理的线程安全方案分析
一、原两种方案的正确性分析
1. 普通HashMap + synchronized(account)方案
该方案存在明显线程安全问题,具体问题点:
- 普通
HashMap本身不支持并发安全,多线程下的get/put操作会引发竞态条件(比如扩容死循环、数据丢失),即使修改Account时加了锁,map的读写仍有安全风险。 accountMap.put(transaction.getAccountNumber(), account)属于冗余操作:Account是引用类型,修改其内部balance属性后,map中存储的引用指向的对象已被更新,无需重新put,反而该操作会引入额外并发风险。- 存在空指针风险:若
accountMap.get()返回null,后续synchronized(account)会直接抛出空指针异常,缺少null校验逻辑。 - 锁对象不稳定:如果其他逻辑将map中的Account实例替换为新对象,当前线程持有的锁会失效,无法保证同一账户的并发修改互斥。
2. 仅使用ConcurrentHashMap方案
该方案同样不满足线程安全要求:
ConcurrentHashMap仅能保证自身get/put等操作的原子性,但Account对象内部的balance修改属于非原子操作。多个线程可同时获取同一Account实例,并发执行balance的读写,会出现竞态条件(比如两个线程同时读取balance=100,一个存100、一个取50,最终balance可能错误地变成50或150),导致数据不一致。
二、修正后的可行方案及其他线程安全实现方式
1. 修正原synchronized方案
- 将accountMap替换为
ConcurrentHashMap<String, Account>,保证map读写操作的线程安全。 - 移除冗余的
accountMap.put操作。 - 增加账户初始化逻辑,确保account不为null。
- 以Account实例作为锁(需保证实例不会被替换)。
修正后代码示例:
private final ConcurrentHashMap<String, Account> accountMap = new ConcurrentHashMap<>(); public Double processTransaction(Transaction transaction) { String accountNumber = transaction.getAccountNumber(); // 初始化账户,避免null Account account = accountMap.computeIfAbsent(accountNumber, k -> new Account(0.0)); synchronized (account) { TransactionType mode = transaction.getTransactionType(); switch (mode) { case DEPOSIT: double balance = account.getBalance(); balance += transaction.getBalance(); account.setBalance(balance); break; case WITHDRAW: double presentBalance = account.getBalance(); if (presentBalance < transaction.getBalance()) { throw new InsufficientFundsException("余额不足"); } presentBalance -= transaction.getBalance(); account.setBalance(presentBalance); break; } return account.getBalance(); } }
2. 将Account设计为线程安全类
直接在Account内部实现线程安全逻辑,外部无需额外加锁:
- 用
AtomicDouble存储balance,利用原子操作保证读写安全; - 或给
getBalance和setBalance方法添加synchronized修饰。
示例(基于AtomicDouble):
public class Account { private final AtomicDouble balance = new AtomicDouble(0.0); public double getBalance() { return balance.get(); } public void deposit(double amount) { balance.addAndGet(amount); } public boolean withdraw(double amount) { while (true) { double current = balance.get(); if (current < amount) { return false; } if (balance.compareAndSet(current, current - amount)) { return true; } } } }
对应交易处理方法:
private final ConcurrentHashMap<String, Account> accountMap = new ConcurrentHashMap<>(); public Double processTransaction(Transaction transaction) { String accountNumber = transaction.getAccountNumber(); Account account = accountMap.computeIfAbsent(accountNumber, k -> new Account()); TransactionType mode = transaction.getTransactionType(); switch (mode) { case DEPOSIT: account.deposit(transaction.getBalance()); break; case WITHDRAW: boolean success = account.withdraw(transaction.getBalance()); if (!success) { throw new InsufficientFundsException("余额不足"); } break; } return account.getBalance(); }
3. 使用细粒度锁(Striped Lock)
当账户数量庞大时,单个锁或每个账户一个锁可能导致锁竞争或内存占用过高,可使用Striped锁(如Guava的Striped类),根据账户号哈希到固定数量的锁上,减少锁竞争:
private final ConcurrentHashMap<String, Account> accountMap = new ConcurrentHashMap<>(); private final Striped<Lock> stripedLock = Striped.lock(16); // 初始化16个锁 public Double processTransaction(Transaction transaction) { String accountNumber = transaction.getAccountNumber(); Account account = accountMap.computeIfAbsent(accountNumber, k -> new Account(0.0)); Lock lock = stripedLock.get(accountNumber); lock.lock(); try { TransactionType mode = transaction.getTransactionType(); switch (mode) { case DEPOSIT: double balance = account.getBalance(); balance += transaction.getBalance(); account.setBalance(balance); break; case WITHDRAW: double presentBalance = account.getBalance(); if (presentBalance < transaction.getBalance()) { throw new InsufficientFundsException("余额不足"); } presentBalance -= transaction.getBalance(); account.setBalance(presentBalance); break; } return account.getBalance(); } finally { lock.unlock(); } }
4. 不可变对象+ConcurrentHashMap原子替换
将Account设计为不可变类,每次修改余额时创建新的Account对象,用ConcurrentHashMap的replace方法原子性替换旧对象,无需额外锁:
public final class Account { private final double balance; public Account(double balance) { this.balance = balance; } public double getBalance() { return balance; } public Account deposit(double amount) { return new Account(this.balance + amount); } public Account withdraw(double amount) throws InsufficientFundsException { if (this.balance < amount) { throw new InsufficientFundsException("余额不足"); } return new Account(this.balance - amount); } }
对应交易处理方法:
private final ConcurrentHashMap<String, Account> accountMap = new ConcurrentHashMap<>(); public Double processTransaction(Transaction transaction) { String accountNumber = transaction.getAccountNumber(); accountMap.putIfAbsent(accountNumber, new Account(0.0)); while (true) { Account currentAccount = accountMap.get(accountNumber); Account newAccount; TransactionType mode = transaction.getTransactionType(); switch (mode) { case DEPOSIT: newAccount = currentAccount.deposit(transaction.getBalance()); break; case WITHDRAW: newAccount = currentAccount.withdraw(transaction.getBalance()); break; default: throw new IllegalArgumentException("无效交易类型"); } // 原子替换,确保无其他线程修改当前账户 if (accountMap.replace(accountNumber, currentAccount, newAccount)) { return newAccount.getBalance(); } } }
内容的提问来源于stack exchange,提问作者Caffeine Coder
相关产品推荐
相关产品推荐

