You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程环境下线程安全方案正确性验证及其他实现思路咨询

多线程环境下交易处理的线程安全方案分析

一、原两种方案的正确性分析

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:23:14