首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程實戰:基於 synchronized 的 ATM 存取款模擬完整代碼

     編輯:Java 相關知識

項目背景

模擬銀行 ATM 系統核心業務:多線程並發存款、取款、查詢餘額。通過 synchronized 關鍵字實現線程安全,演示線程生命周期、狀態轉換、同步機制等核心概念。

領域模型:賬戶類

public class Account {
    private final String name;     // 用戶名
    private int balance;           // 餘額(非 volatile,靠 synchronized 保證可見性)

    public Account(String name, int initialBalance) {
        this.name = name;
        this.balance = initialBalance;
    }

    /** 存款:線程安全 */
    public synchronized void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("存款金額必須大於 0");
        int prev = balance;
        balance += amount;
        log("存款", prev, amount, balance);
    }

    /** 取款:線程安全,餘額不足時取走剩餘所有 */
    public synchronized int withdraw(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("取款金額必須大於 0");
        int prev = balance;
        int actual = Math.min(amount, balance); // 餘額不足全取
        balance -= actual;
        log("取款", prev, actual, balance);
        return actual;
    }

    /** 查詢餘額:線程安全 */
    public synchronized int getBalance() {
        return balance;
    }

    public String getName() { return name; }

    private void log(String op, int prev, int amt, int after) {
        System.out.printf("[%s] %s: 戶主=%s, 操作前=%d, 金額=%d, 操作後=%d%n",
                Thread.currentThread().getName(), op, name, prev, amt, after);
    }
}

線程任務類

存款線程

public class DepositTask implements Runnable {
    private final Account account;
    private final int amount;

    public DepositTask(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void run() {
        // 模擬網絡/處理延遲
        sleepRandom(5, 20);
        account.deposit(amount);
    }

    private void sleepRandom(int min, int max) {
        try { Thread.sleep((long) (Math.random() * (max - min + 1) + min)); }
        catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

取款線程

public class WithdrawTask implements Runnable {
    private final Account account;
    private final int amount;

    public WithdrawTask(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void run() {
        sleepRandom(5, 20);
        int actual = account.withdraw(amount);
        System.out.printf("[%s] 取款完成: 請求=%d, 實際到手=%d%n",
                Thread.currentThread().getName(), amount, actual);
    }

    private void sleepRandom(int min, int max) {
        try { Thread.sleep((long) (Math.random() * (max - min + 1) + min)); }
        catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

主程序啟動與驗證

public class AtmSimulation {
    public static void main(String[] args) {
        Account account = new Account("張三", 0); // 初始餘額 0

        // 創建並啟動線程
        Thread t1 = new Thread(new DepositTask(account, 100), "存款線程-1");
        Thread t2 = new Thread(new DepositTask(account, 200), "存款線程-2");
        Thread t3 = new Thread(new WithdrawTask(account, 500), "取款線程-1");

        t1.start();
        t2.start();
        t3.start();

        // 等待所有線程結束
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("=== 所有操作完成 ===");
        System.out.printf("最終餘額: %d%n", account.getBalance());
    }
}

運行結果示例(每次執行順序可能不同)

[存款線程-1] 存款: 戶主=張三, 操作前=0, 金額=100, 操作後=100
[存款線程-2] 存款: 戶主=張三, 操作前=100, 金額=200, 操作後=300
[取款線程-1] 取款: 戶主=張三, 操作前=300, 金額=300, 操作後=0
[取款線程-1] 取款完成: 請求=500, 實際到手=300
=== 所有操作完成 ===
最終餘額: 0

核心知識點解析

1. 線程生命周期五大狀態

狀態觸發條件代碼體現
NEWnew Thread()創建 DepositTask 實例
RUNNABLEstart()t1.start() 後,等待 CPU 調度
RUNNING獲得 CPU 時間片執行 run() 中的業務邏輯
BLOCKED嘗試進入 synchronized 方法但鎖被佔用取款線程等待存款線程釋放 account 鎖
TERMINATEDrun() 正常返回或拋出未捕獲異常各線程執行完畢

2. synchronized 保證的三大特性

  • 原子性:deposit/withdraw/getBalance 互斥執行,不會出現「讀取舊值 → 計算 → 寫回」被打斷導致數據不一致。
  • 可見性:線程解鎖前將工作內存修改刷新到主內存,加鎖後從主內存讀取最新值。
  • 有序性:禁止指令重排序優化破壞邏輯順序。

3. 為什麼不用 extends Thread?

  • Java 單繼承限制:繼承 Thread 就無法繼承業務基類。
  • 實現 Runnable 解耦「任務邏輯」與「線程控制」,同一任務可提交給線程池、CompletableFuture 等不同執行器。
  • 符合「組合優於繼承」原則。

4. 進階改進方向

  1. ReentrantLock 替代 synchronized:支持可中斷鎖、公平鎖、多條件變量(Condition)。
  2. 線程池管理線程:ExecutorService pool = Executors.newFixedThreadPool(10); pool.submit(task); 避免頻繁創建銷毀線程開銷。
  3. 原子類:單純計數器場景用 AtomicInteger 無鎖 CAS 效能更高。
  4. 事務邊界:真實銀行系統需引入分布式事務(兩階段提交/TCC/Saga)保證跨服務一致性。

總結

本例通過 Account 封裝共享狀態,synchronized 實例方法保護臨界區,Runnable 任務提交給線程執行,完整演示了 Java 多線程並發控制的基礎模式。理解「共享可變狀態 + 互斥同步」是並發編程的入門鑰匙,進階學習請關注 java.util.concurrent 包下的高級組件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved