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

Java 多線程實戰:基於 Runnable 實現的 ATM 存取款模擬

     編輯:Java 程式設計入門

為什麼選擇實現 Runnable?

當線程類已繼承其他類時,無法繼承 Thread,只能實現 Runnable;且統一實現介面風格,便於線程池提交、解耦任務與執行機制。

線程五大狀態速覽

  • 新建:new Thread(),未獲取資源
  • 可運行:start() 後,就緒等待 CPU
  • 運行中:佔用 CPU,執行 run()
  • 阻塞:等待鎖、I/O、sleep、wait
  • 死亡:run() 結束或異常終止

核心類設計

1. 賬戶類 - 共享資源

public class Account {
    private String name;
    private int balance; // 余額

    public synchronized void deposit(int amount) { // 存款
        balance += amount;
    }

    public synchronized int withdraw(int amount) { // 取款
        if (balance >= amount) {
            balance -= amount;
            return amount;
        } else {
            int all = balance;
            balance = 0;
            return all; // 余額不足取走全部
        }
    }

    public synchronized int getBalance() { return balance; }
    // getter/setter 略
}

關鍵:所有操作共享資源的方法加 synchronized,保證原子性與可見性。

2. 存款線程

public class SaveMoney extends Thread {
    private Account account;
    private int amount;

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

    @Override
    public void run() {
        int before = account.getBalance();
        synchronized (account) { // 雙重保險:方法鎖 + 代碼塊鎖
            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            account.deposit(amount);
            System.out.printf("用戶 %s 存款 %d,存前余額 %d,存後余額 %d%n",
                account.getName(), amount, before, account.getBalance());
        }
    }
}

3. 取款線程

public class FetchMoney extends Thread {
    private Account account;
    private int amount;

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

    @Override
    public void run() {
        int before = account.getBalance();
        synchronized (account) {
            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            int got = account.withdraw(amount);
            System.out.printf("用戶 %s 取款 %d,取前余額 %d,實取 %d,取後余額 %d%n",
                account.getName(), amount, before, got, account.getBalance());
        }
    }
}

4. 測試類:模擬併發存取

public class ThreadATMDemo {
    public static void main(String[] args) {
        Account acc = new Account();
        acc.setName("ZhouHaiTao");
        acc.setValue(0);

        new SaveMoney(acc, 100).start(); // 存 100
        new SaveMoney(acc, 200).start(); // 存 200
        new FetchMoney(acc, 500).start(); // 取 500(余額不足)
    }
}

運行結果分析

用戶 ZhouHaiTao 存款 100,存前余額 0,存後余額 100
用戶 ZhouHaiTao 存款 200,存前余額 0,存後余額 300
用戶 ZhouHaiTao 取款 500,取前余額 0,實取 300,取後余額 0

輸出順序不固定(取決於線程調度),但余額計算始終正確,證明 synchronized 正確保護了臨界區。

進階優化提示

  • 生產環境建議使用 ReentrantLock + Condition 替代 synchronized,支援公平鎖、可中斷等待、多條件變量
  • 金額建議用 BigDecimal 避免浮點誤差
  • 引入線程池管理線程生命週期,避免頻繁創建銷毀開銷
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved