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

Java 並發鎖機制深度解析:從 synchronized 到 Lock、可重入、公平性與最佳實踐

     編輯:Java 綜合教學

前言

Java 5 引入 java.util.concurrent.locks 包,提供了比 synchronized 更靈活、功能更豐富的鎖機制。本文從簡單鎖實現入手,層層遞進講解**可重入性、公平性、條件變量、中斷響應**等核心特性,並給出生產環境的最佳實踐指南。

一、基礎:synchronized vs Lock

1. synchronized 內置鎖

public class Counter {
    private int count = 0;
    public int inc() {
        synchronized(this) { return ++count; }
    }
}
  • 隱式獲取/釋放鎖,語法簡潔
  • 無法中斷等待、無法設超時、無法公平、無法綁定多條件

2. 顯式 Lock 接口

public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count = 0;
    public int inc() {
        lock.lock();
        try { return ++count; }
        finally { lock.unlock(); }
    }
}
  • 手動 lock()/unlock(),**必須在 finally 中釋放**
  • 支持 tryLock()、lockInterruptibly()、newCondition()、公平/非公平選擇

二、手寫簡易鎖理解核心原理

1. 自旋鎖

public class SimpleLock {
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException {
        while (isLocked) wait();  // 自旋等待
        isLocked = true;
    }
    public synchronized void unlock() {
        isLocked = false;
        notify();                 // 喚醒一個等待線程
    }
}

關鍵點:while(isLocked) 防止**虛假喚醒**(spurious wakeup);wait() 釋放監視器鎖並進入等待集合;notify() 喚醒一個線程重新競爭。

2. 可重入鎖實現

public class ReentrantSimpleLock {
    private boolean isLocked = false;
    private Thread lockedBy = null;
    private int lockedCount = 0;
    public synchronized void lock() throws InterruptedException {
        Thread t = Thread.currentThread();
        while (isLocked && lockedBy != t) wait(); // 允許同線程重入
        isLocked = true;
        lockedBy = t;
        lockedCount++;
    }
    public synchronized void unlock() {
        if (Thread.currentThread() == lockedBy) {
            if (--lockedCount == 0) {
                isLocked = false;
                lockedBy = null;
                notify();
            }
        }
    }
}

核心邏輯:記錄**持有線程**與**重入計數**,只有計數歸零才真正釋放鎖。

三、Java 內置 Lock 實現:ReentrantLock 源碼精髓

1. 雙重隊列同步器(AQS)

ReentrantLock 內部持有 Sync extends AbstractQueuedSynchronizer,利用 AQS 的**狀態變量 state** 表示鎖計數,**FIFO 隊列**管理等待線程。

2. 非公平 vs 公平

特性非公平鎖(默認)公平鎖
獲取邏輯直接 CAS 嘗試搶佔,失敗再入隊檢查隊列頭是否為自己,否則入隊等待
吞吐量高(減少上下文切換)低(嚴格排隊)
飢餓風險有(新線程可能連續搶佔)無(先來先服務)
適用場景高併發、非關鍵路徑嚴格時序要求、避免飢餓
// 創建公平鎖
Lock fairLock = new ReentrantLock(true);

3. 核心方法語義

  • lock():阻塞獲取,不可中斷
  • lockInterruptibly():阻塞獲取,**可被中斷**(拋出 InterruptedException),適合取消長時間等待
  • tryLock():非阻塞嘗試,立即返回 boolean
  • tryLock(timeout, unit):限時嘗試,支持中斷
  • newCondition():綁定條件變量,實現精細的 await/signal

四、條件變量:比 wait/notify 更強大

class BoundedBuffer<T> {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final T[] items = (T[]) new Object[10];
    private int putIdx = 0, takeIdx = 0, count = 0;

    public void put(T x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) notFull.await();
            items[putIdx] = x;
            if (++putIdx == items.length) putIdx = 0;
            ++count;
            notEmpty.signal();
        } finally { lock.unlock(); }
    }
    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) notEmpty.await();
            T x = items[takeIdx];
            if (++takeIdx == items.length) takeIdx = 0;
            --count;
            notFull.signal();
            return x;
        } finally { lock.unlock(); }
    }
}

優勢:一個鎖可綁定多個條件隊列,精確喚醒生產者/消費者,避免 notifyAll() 的驚群效應。

五、ReadWriteLock:讀寫分離提升並發

class CachedData {
    private final Map<String, String> map = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock r = rwLock.readLock();
    private final Lock w = rwLock.writeLock();

    public String get(String key) {
        r.lock();
        try { return map.get(key); }
        finally { r.unlock(); }
    }
    public String put(String key, String value) {
        w.lock();
        try { return map.put(key, value); }
        finally { w.unlock(); }
    }
}
  • 讀鎖共享(多線程並發讀),寫鎖獨佔
  • 支持降級(寫鎖→讀鎖),不支持升級
  • 適用:讀多寫少、數據結構不可變或低頻更新場景

六、生產環境最佳實踐清單

  1. 永遠在 finally 中 unlock():防止異常導致死鎖
  2. 優先使用 try-with-resources 風格封裝:
    public static <T> T withLock(Lock lock, Callable<T> action) throws Exception {
        lock.lock();
        try { return action.call(); }
        finally { lock.unlock(); }
    }
  3. 避免在鎖內調用外部方法/IO/睡眠:縮小臨界區,降低爭用
  4. 選擇合適鎖類型:ReentrantLock 通用、ReentrantReadWriteLock 讀多寫少、StampedLock(Java 8)樂觀讀更高性能
  5. 公平鎖慎用:除非業務強要求先來先服務,否則默認非公平鎖吞吐更高
  6. 避免鎖嵌套導致死鎖:固定獲取順序、使用 tryLock(timeout) 檢測、考慮無鎖數據結構(ConcurrentHashMap、LongAdder)
  7. 監控鎖爭用:JDK Flight Recorder / Arthas monitor / Prometheus 自定義指標

七、總結

從 synchronized 到 Lock,Java 鎖機制經歷了**隱式→顯式、單一→多條件、非公平→可選公平、獨佔→讀寫分離**的演進。掌握 AQS 狀態機、隊列同步、條件變量原理,能讓你在面對高併發、死鎖、性能瓶頸時**有的放矢**。記住:**最好的鎖是不需要鎖的**(無鎖算法、線程封閉、不可變對象),其次是**用對鎖、用好鎖、及時釋放鎖**。

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