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

Java多线程共享银行账户同步实现求助(课程作业)

Hey there! Let's tackle your multi-threaded bank account problem step by step. First, I noticed a critical semantic bug in your withdraw method (even though the math might work, it's confusing), then we'll fix thread safety with synchronization, and finally polish up your code for better maintainability.


1. Fix Critical Withdraw Method Bug

Your current withdraw method adds a negative value to the balance, which technically works, but the method name and output are misleading. Let's make the logic clearer and fix the output:

// Original flawed method
public void withdraw(double value) {
 accountBalance = (accountBalance + value);
 System.out.println("Account Balance After Withdrawal: " + accountBalance);
}

// Fixed version (keeps your transaction list convention but clarifies output)
public void withdraw(double value) {
    accountBalance += value;
    System.out.printf("Withdrew %.2f | New Balance: %.2f%n", Math.abs(value), accountBalance);
}

2. Implement Synchronization for Thread Safety

Since multiple threads are modifying the same BankAccount balance, we need to ensure these operations are atomic (no race conditions) and that balance updates are visible to all threads. Here are two reliable approaches:

Option 1: Synchronized Methods (Simple & Effective)

Add the synchronized keyword to methods that modify or read the balance. This ensures only one thread can execute these methods at a time:

public class BankAccount {
    private long accountNo;
    private double accountBalance;

    public BankAccount() {
        this.accountNo = 9876543210L;
        this.accountBalance = 1980.0;
    }

    public synchronized void getAccountInfo() {
        System.out.printf("Account Number: %d%nCurrent Balance: %.2f%n", accountNo, accountBalance);
    }

    public synchronized double getAccountBalance() {
        return accountBalance;
    }

    public synchronized void deposit(double value) {
        accountBalance += value;
        System.out.printf("Deposited %.2f | New Balance: %.2f%n", value, accountBalance);
    }

    public synchronized void withdraw(double value) {
        accountBalance += value;
        System.out.printf("Withdrew %.2f | New Balance: %.2f%n", Math.abs(value), accountBalance);
    }
}

Option 2: ReentrantLock (More Flexible)

If you need advanced control (like timeouts for locking), use ReentrantLock from the java.util.concurrent.locks package:

import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {
    private long accountNo;
    private double accountBalance;
    private final ReentrantLock lock = new ReentrantLock();

    public BankAccount() {
        this.accountNo = 9876543210L;
        this.accountBalance = 1980.0;
    }

    public void getAccountInfo() {
        lock.lock();
        try {
            System.out.printf("Account Number: %d%nCurrent Balance: %.2f%n", accountNo, accountBalance);
        } finally {
            lock.unlock(); // Always unlock in finally to avoid deadlocks
        }
    }

    public double getAccountBalance() {
        lock.lock();
        try {
            return accountBalance;
        } finally {
            lock.unlock();
        }
    }

    public void deposit(double value) {
        lock.lock();
        try {
            accountBalance += value;
            System.out.printf("Deposited %.2f | New Balance: %.2f%n", value, accountBalance);
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(double value) {
        lock.lock();
        try {
            accountBalance += value;
            System.out.printf("Withdrew %.2f | New Balance: %.2f%n", Math.abs(value), accountBalance);
        } finally {
            lock.unlock();
        }
    }
}

3. Code Optimization Suggestions

a. Avoid Repeated Scanner Creation

You're creating a new Scanner every time you show a menu. Instead, create one static instance:

public class BankTest {
    private static final Scanner scanner = new Scanner(System.in);
    // Rest of your code...
}

b. Thread-Safe Console Output

Multiple threads printing at the same time causes messy logs. Wrap all print statements in a synchronized block using a shared lock:

private static final Object printLock = new Object();

// Use this whenever you print from threads:
synchronized(printLock) {
    System.out.printf("[%s] %s: %s%.2f | Balance: %.2f%n", 
        Thread.currentThread().getName(),
        userName,
        transactionType,
        amount,
        balance);
}

c. Add Null Checks to Prevent Crashes

Users might try to run the simulation before creating the account or all users. Add checks to avoid NullPointerException:

case 3:
    if (myAccount == null) {
        System.err.println("Error: Create a bank account first!");
        break;
    }
    if (myUser1 == null || myUser2 == null || myUser3 == null || myUser4 == null) {
        System.err.println("Error: Create all 4 users first!");
        break;
    }
    // Start threads...

d. Improve Transaction Tracking

Your requirement says "each user's transaction存于数组中" (each user's transactions stored in an array). Let's expand the User class to track detailed transaction history:

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class User {
    private String name;
    private String surname;
    private BankAccount bankAccount;
    private List<Transaction> transactions;

    public User(String n, String s, BankAccount bA, double[] tL) {
        this.name = n;
        this.surname = s;
        this.bankAccount = bA;
        this.transactions = new ArrayList<>();
        for (double amount : tL) {
            transactions.add(new Transaction(amount));
        }
    }

    public void executeTransactions() {
        for (Transaction t : transactions) {
            double amount = t.getAmount();
            if (amount < 0) {
                bankAccount.withdraw(amount);
                t.setType(Transaction.Type.WITHDRAWAL);
            } else {
                bankAccount.deposit(amount);
                t.setType(Transaction.Type.DEPOSIT);
            }
            t.setNewBalance(bankAccount.getAccountBalance());
            t.setTimestamp(LocalDateTime.now());

            // Thread-safe print
            synchronized(System.out) {
                System.out.printf("[%s] %s %s: %s %.2f | Balance: %.2f%n",
                    Thread.currentThread().getName(),
                    name, surname,
                    t.getType().name(),
                    Math.abs(amount),
                    t.getNewBalance());
            }
        }
    }

    // Getters and nested Transaction class
    public static class Transaction {
        public enum Type {DEPOSIT, WITHDRAWAL}
        private double amount;
        private Type type;
        private double newBalance;
        private LocalDateTime timestamp;

        public Transaction(double amount) {
            this.amount = amount;
        }

        // Getters and Setters
        public double getAmount() { return amount; }
        public Type getType() { return type; }
        public void setType(Type type) { this.type = type; }
        public double getNewBalance() { return newBalance; }
        public void setNewBalance(double newBalance) { this.newBalance = newBalance; }
        public LocalDateTime getTimestamp() { return timestamp; }
        public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    }

    // Existing getUserInfo, getName, etc.
}

e. Use ExecutorService for Better Thread Management

Instead of manually starting threads, use ExecutorService to manage a thread pool (cleaner and more efficient):

case 3:
    // ... null checks
    ExecutorService executor = Executors.newFixedThreadPool(4);
    executor.submit(myUser1);
    executor.submit(myUser2);
    executor.submit(myUser3);
    executor.submit(myUser4);
    executor.shutdown();

    // Wait for all threads to finish
    try {
        executor.awaitTermination(1, TimeUnit.MINUTES);
        System.out.println("\nAll transactions completed!");
        myAccount.getAccountInfo();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    break;

4. Full Optimized & Synchronized Code

Here's the complete code with all fixes and optimizations applied:

BankAccount.java

public class BankAccount {
    private long accountNo;
    private double accountBalance;

    public BankAccount() {
        this.accountNo = 9876543210L;
        this.accountBalance = 1980.0;
    }

    public synchronized void getAccountInfo() {
        System.out.printf("\n=== Final Account Status ===%n");
        System.out.printf("Account Number: %d%nFinal Balance: %.2f%n", accountNo, accountBalance);
    }

    public synchronized double getAccountBalance() {
        return accountBalance;
    }

    public synchronized void deposit(double value) {
        accountBalance += value;
    }

    public synchronized void withdraw(double value) {
        accountBalance += value;
    }
}

User.java

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class User {
    private String name;
    private String surname;
    private BankAccount bankAccount;
    private List<Transaction> transactions;

    public User(String n, String s, BankAccount bA, double[] tL) {
        this.name = n;
        this.surname = s;
        this.bankAccount = bA;
        this.transactions = new ArrayList<>();
        for (double amount : tL) {
            transactions.add(new Transaction(amount));
        }
    }

    public void executeTransactions() {
        for (Transaction t : transactions) {
            double amount = t.getAmount();
            if (amount < 0) {
                bankAccount.withdraw(amount);
                t.setType(Transaction.Type.WITHDRAWAL);
            } else {
                bankAccount.deposit(amount);
                t.setType(Transaction.Type.DEPOSIT);
            }
            t.setNewBalance(bankAccount.getAccountBalance());
            t.setTimestamp(LocalDateTime.now());

            // Thread-safe output
            synchronized (System.out) {
                System.out.printf("[%s] %s %s: %s %.2f | Balance: %.2f%n",
                        Thread.currentThread().getName(),
                        name, surname,
                        t.getType().name(),
                        Math.abs(amount),
                        t.getNewBalance());
            }
        }
    }

    public void getUserInfo() {
        System.out.printf("Created User: %s %s%n", name, surname);
    }

    public static class Transaction {
        public enum Type {DEPOSIT, WITHDRAWAL}
        private double amount;
        private Type type;
        private double newBalance;
        private LocalDateTime timestamp;

        public Transaction(double amount) {
            this.amount = amount;
        }

        // Getters and Setters
        public double getAmount() { return amount; }
        public Type getType() { return type; }
        public void setType(Type type) { this.type = type; }
        public double getNewBalance() { return newBalance; }
        public void setNewBalance(double newBalance) { this.newBalance = newBalance; }
        public LocalDateTime getTimestamp() { return timestamp; }
        public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    }
}

UserThread.java

public class UserThread extends Thread {
    private final User user;

    public UserThread(User user, String threadName) {
        super(threadName);
        this.user = user;
    }

    @Override
    public void run() {
        user.executeTransactions();
    }
}

BankTest.java

import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class BankTest {
    private static final Scanner scanner = new Scanner(System.in);
    private static final double[][] TRANSACTION_LISTS = {
            {50, 10, -20, 10, -20, 20, 10, 50, -10, 10, -10, 50},
            {20, 20, -20, 50, -20, 10, 50, 50, -20, 10, 10},
            {50, 10, 10, -10, -10, 50, 20, -10, -20},
            {50, 10, -20, 20, 10, -20}
    };

    private static BankAccount bankAccount;
    private static UserThread user1Thread;
    private static UserThread user2Thread;
    private static UserThread user3Thread;
    private static UserThread user4Thread;

    public static void main(String[] args) {
        while (true) {
            printMainMenu();
            int choice = scanner.nextInt();
            scanner.nextLine(); // Consume leftover newline

            switch (choice) {
                case 1:
                    createBankAccount();
                    break;
                case 2:
                    showUserCreationMenu();
                    break;
                case 3:
                    runSimulation();
                    break;
                case 4:
                    System.out.println("Goodbye!");
                    scanner.close();
                    System.exit(0);
                default:
                    System.err.println("Invalid option. Please try again.");
            }
        }
    }

    private static void printMainMenu() {
        System.out.println("\n<--------- Banking Menu ----------->");
        System.out.println("1. Create Bank Account");
        System.out.println("2. Create User");
        System.out.println("3. Run Simulation");
        System.out.println("4. Exit");
        System.out.print("Enter choice: ");
    }

    private static void createBankAccount() {
        System.out.println("\n<--------- Create Bank Account ----------->");
        bankAccount = new BankAccount();
        System.out.println("Bank Account Created Successfully!");
        bankAccount.getAccountInfo();
    }

    private static void showUserCreationMenu() {
        System.out.println("\n<--------- Create User ----------->");
        System.out.println("1. User 1");
        System.out.println("2. User 2");
        System.out.println("3. User 3");
        System.out.println("4. User 4");
        System.out.print("Enter user number: ");

        int userChoice = scanner.nextInt();
        scanner.nextLine();

        if (bankAccount == null) {
            System.err.println("Error: Please create a bank account first!");
            return;
        }

        switch (userChoice) {
            case 1:
                User user1 = new User("Saul", "Goodman", bankAccount, TRANSACTION_LISTS[0]);
                user1Thread = new UserThread(user1, "User-1-Thread");
                user1.getUserInfo();
                break;
            case 2:
                User user2 = new User("Walter", "White", bankAccount, TRANSACTION_LISTS[1]);
                user2Thread = new UserThread(user2, "User-2-Thread");
                user2.getUserInfo();
                break;
            case 3:
                User user3 = new User("Jessie", "Pinkman", bankAccount, TRANSACTION_LISTS[2]);
                user3Thread = new UserThread(user3, "User-3-Thread");
                user3.getUserInfo();
                break;
            case 4:
                User user4 = new User("Hank", "Schrader", bankAccount, TRANSACTION_LISTS[3]);
                user4Thread = new UserThread(user4, "User-4-Thread");
                user4.getUserInfo();
                break;
            default:
                System.err.println("Invalid user number.");
        }
    }

    private static void runSimulation() {
        if (bankAccount == null) {
            System.err.println("Error: Please create a bank account first!");
            return;
        }
        if (user1Thread == null || user2Thread == null || user3Thread == null || user4Thread == null) {
            System.err.println("Error: Please create all 4 users first!");
            return;
        }

        System.out.println("\n=== Starting Simulation ===");
        ExecutorService executor = Executors.newFixedThreadPool(4);
        executor.submit(user1Thread);
        executor.submit(user2Thread);
        executor.submit(user3Thread);
        executor.submit(user4Thread);

        executor.shutdown();
        try {
            if (executor.awaitTermination(1, TimeUnit.MINUTES)) {
                bankAccount.getAccountInfo();
            } else {
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:37:28