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

Java多线程环境下账户转账实现正确性排查及优化问询

Hey there, let's break down what's going wrong with your multi-threaded transfer setup and how to fix it to keep that total account balance rock-solid consistent!

Core Issues in Your Current Implementation

Let's start with the biggest red flags that are causing your balance drift:

  1. Java Object Locks Are Useless for Database Sync
    You're trying to synchronize on Account objects loaded from Hibernate, but every call to openSession().get(...) creates a new Session, which loads a fresh, independent instance of the Account. That means Thread A's lock on "Account 1" doesn't affect Thread B's lock on the same database account (they're different Java objects!). Your initial "success" with small thread counts/slow execution was just luck—threads weren't overlapping, not because the locks worked.

  2. Misaligned Transactions & Sessions
    You load accounts in separate Sessions, modify them in a sync block, then open another Session to commit changes. This breaks ACID guarantees: there's no link between the modified objects and the final update Session, so Hibernate can't track changes properly, leading to dirty writes.

  3. Account Method Logic Is Reversed
    This is a critical bug that's hiding in plain sight:

    • widrawal(int sum) adds money to the account (should subtract for a withdrawal)
    • send(int sum) subtracts money from the account (should add for a deposit)
      While this might keep total balance the same on paper, it completely reverses your transfer direction and makes your balance check logic misleading.

Fixed Implementation

Let's rewrite the key parts to fix these issues, using database-level locking and proper Hibernate transaction management.

1. Corrected Account Entity

First, fix the method logic and naming:

@Entity
public class Account {
    @Id
    private int id;
    private int money;

    public Account() { }

    // Withdraw (remove funds from this account)
    public void withdrawal(int sum) { 
        money -= sum; 
    }

    // Deposit (add funds to this account)
    public void deposit(int sum) { 
        money += sum; 
    }

    // Getters & Setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public int getMoney() { return money; }
    public void setMoney(int money) { this.money = money; }
}

2. Transfer Logic with Pessimistic Locking

We'll use database-level pessimistic write locks to ensure only one thread can modify an account at a time, and wrap the entire operation in a single transaction:

public class Transfer {
    public void transaction() {
        int transferAmount = (int) (Math.random() * 100) + 10;
        int fromId = (int) (Math.random() * 50) + 1;
        int toId = (int) (Math.random() * 50) + 1;

        if (fromId == toId) {
            System.out.println("Skipping transfer: same account ID");
            return;
        }

        // Enforce lock order (smaller ID first) to avoid deadlocks
        int firstId = Math.min(fromId, toId);
        int secondId = Math.max(fromId, toId);
        boolean isFromFirst = fromId == firstId;

        Session session = null;
        Transaction tx = null;

        try {
            session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
            tx = session.beginTransaction();

            // Load accounts with pessimistic write locks (locks the database row)
            Account fromAccount = session.get(Account.class, isFromFirst ? firstId : secondId, LockMode.PESSIMISTIC_WRITE);
            Account toAccount = session.get(Account.class, isFromFirst ? secondId : firstId, LockMode.PESSIMISTIC_WRITE);

            // Validate sufficient funds
            if (fromAccount.getMoney() < transferAmount) {
                System.out.println("Insufficient funds: Account " + fromAccount.getId() + " has " + fromAccount.getMoney() + ", needs " + transferAmount);
                tx.rollback();
                return;
            }

            // Execute transfer
            fromAccount.withdrawal(transferAmount);
            toAccount.deposit(transferAmount);

            // Hibernate auto-tracks changes from the same session—no need for manual update
            tx.commit();
            System.out.println("Successfully transferred " + transferAmount + " from Account " + fromAccount.getId() + " to Account " + toAccount.getId());
        } catch (Exception e) {
            if (tx != null) tx.rollback();
            System.err.println("Transfer failed: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (session != null) session.close();
        }
    }
}

3. Simplified Transfer Thread

No need for AtomicInteger—just a straightforward loop, and handle interrupts properly:

public class TransferThread extends Thread {
    private final Transfer transfer = new Transfer();

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                transfer.transaction();
                // Random sleep between 100-500ms
                int sleepTime = (int) (Math.random() * 400) + 100;
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread interrupted, stopping transfers");
                break;
            }
        }
    }
}

Key Takeaways to Keep Balance Consistent

  • Use Database-Level Locking: Never rely on Java object locks for database sync—Hibernate creates object copies per Session, so those locks don't cross thread boundaries. Pessimistic locks (LockMode.PESSIMISTIC_WRITE) ensure the database itself enforces mutual exclusion.
  • Wrap Everything in One Transaction: Load, modify, and commit must happen in the same Session/transaction to guarantee ACID compliance.
  • Enforce Lock Order: Sort account IDs before locking to avoid deadlocks (two threads waiting for each other's locks).
  • Handle Exceptions & Rollbacks: Always roll back transactions on failure to prevent partial updates from corrupting your data.

内容的提问来源于stack exchange,提问作者Yury

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 12:02:52