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

Lock 同步鎖深度解析:ReentrantLock 實現線程安全取款案例

     編輯:Java 綜合教學

Lock 體系核心優勢

Java 5 引入 java.util.concurrent.locks.Lock 介面,提供比 synchronized 更靈活的同步控制:

  • 顯式加鎖/解鎖:lock()/unlock(),支援非塊結構(跨方法加解鎖)。
  • 可中斷獲取:lockInterruptibly(),等待鎖時響應中斷。
  • 嘗試非阻塞獲取:tryLock()/tryLock(timeout, unit),獲取失敗立即返回或超時返回。
  • 公平/非公平選擇:new ReentrantLock(true) 公平鎖,避免饑餓。
  • 多條件變量:newCondition() 實現精細化等待/通知(vs Object.wait/notify 單一條件)。

ReentrantLock 可重入特性

同一線程可重複獲取同一把鎖,內部維護 state 計數器,lock() 增、unlock() 減,歸零釋放。必須配對調用,建議放在 finally 中保證釋放。

經典案例:銀行賬戶並發取款

public class Account {
    private final ReentrantLock lock = new ReentrantLock(); // 最終引用,不可變
    private String accountNo;
    private double balance;

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public void draw(double amount) {
        lock.lock(); // 1. 加鎖
        try {
            if (balance >= amount) {
                System.out.println(Thread.currentThread().getName() + " 取款成功:" + amount);
                Thread.sleep(1); // 模擬耗時
                balance -= amount;
                System.out.println("\t餘額:" + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 取款失敗:餘額不足");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保留中斷狀態
        } finally {
            lock.unlock(); // 2. 釋放鎖(必須在 finally)
        }
    }
    // equals/hashCode 基於 accountNo
}
public class DrawThread extends Thread {
    private Account account;
    private double amount;
    public DrawThread(String name, Account account, double amount) {
        super(name); this.account = account; this.amount = amount;
    }
    @Override public void run() { account.draw(amount); }
}
public class TestDraw {
    public static void main(String[] args) {
        Account acct = new Account("1234567", 1000);
        new DrawThread("甲", acct, 800).start();
        new DrawThread("乙", acct, 800).start();
    }
}

執行結果

甲 取款成功:800.0
        餘額:200.0
乙 取款失敗:餘額不足

兩線程共享同一 Account 實例 → 共享同一 ReentrantLock → 同一時刻僅一線程進入 draw() 臨界區 → 餘額檢查與扣減原子化。

進階用法速覽

最佳實踐清單

  1. 鎖對象宣告 final,防止引用變更導致同步失效。
  2. 嚴格 lock() → try → finally unlock() 模式。
  3. 優先考慮 synchronized(JVM 優化成熟、語法簡潔),僅在需要 Lock 進階特性時使用。
  4. 避免在鎖內調用外部方法/耗時 IO,縮小臨界區。
  5. 多把鎖時固定獲取順序,防死鎖;或用 tryLock 超時退避。

掌握 ReentrantLock 核心模式與進階 API,是編寫高併發組件(連線池、快取、限流器)的必備技能。下一步可探討 ReadWriteLock、StampedLock 及 AQS 框架原理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方法場景
tryLock()非阻塞嘗試,失敗立即執行降級邏輯(如顯示「系統繁忙」)。
tryLock(3, TimeUnit.SECONDS)限時等待,避免無限阻塞,超時走補償流程。
lockInterruptibly()可中斷等待,支援取消操作(如用戶點「取消」按鈕)。
newCondition()生產者/消費者模型:notFull.await()/notEmpty.signal()。
isHeldByCurrentThread()診斷鎖持有狀態,防死鎖調試。