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

Java 鎖機制深度解析:synchronized、Lock、AQS 與併發最佳實踐

     編輯:Java 相關知識

Java 鎖體系全景

Java 鎖
├── 隱式鎖
│   └── synchronized (關鍵字、JVM 級別)
│       ├── 對象鎖 (實例方法、同步塊 this)
│       └── 類鎖 (靜態方法、同步塊 Class 對象)
│
└── 顯式鎖
    └── java.util.concurrent.locks.Lock (介面)
        ├── ReentrantLock (可重入鎖)
        ├── ReentrantReadWriteLock (讀寫鎖)
        ├── StampedLock (樂觀讀鎖, Java 8+)
        └── 自定義鎖 (基於 AQS)

synchronized 深度剖析

三種使用形式

// 1. 修飾實例方法 -> 鎖住 this
public synchronized void instanceMethod() { }

// 2. 修飾靜態方法 -> 鎖住 Class 對象
public static synchronized void staticMethod() { }

// 3. 同步塊 -> 鎖住指定對象 (粒度更細)
public void blockSync() {
    synchronized (lockObject) {  // 也可以用 this、Class.forName("xxx")
        // 臨界區
    }
}

核心特性

鎖升級優化 (JDK 1.6+)

無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
   (單線程)   (CAS自旋)    (OS互斥量)
  • 偏向鎖:首次獲取鎖的線程在對象頭標記 Thread ID,後續進入無需 CAS
  • 輕量級鎖:多線程交替訪問,用 CAS 修改對象頭指向棧帧鎖記錄
  • 重量級鎖:競爭激烈,膨脹為 ObjectMonitor,涉及 OS 內核態切換

ReentrantLock:顯式鎖標杆

核心 API

Lock lock = new ReentrantLock();

// 基礎用法 (必須 try-finally)
lock.lock();
try {
    // 臨界區
} finally {
    lock.unlock();
}

// 進階特性
lock.lockInterruptibly();           // 可中斷獲取
lock.tryLock();                     // 非阻塞嘗試
lock.tryLock(5, TimeUnit.SECONDS);  // 超時嘗試
Condition cond = lock.newCondition(); // 精確等待/通知
cond.await();
cond.signal();

公平 vs 非公平

// 公平鎖:嚴格按請求順序獲取,吞吐率低但無餓死
Lock fairLock = new ReentrantLock(true);

// 非公平鎖:默認,搶佔式,吞吐率高
Lock unfairLock = new ReentrantLock(false);

ReadWriteLock:讀寫分離利器

適用場景

讀多寫少、讀操作耗時長 (如緩存、配置、搜索索引)

核心實現原理

  • 單一 `state` 變量:高 16 位 = 讀狀態 (共享鎖計數),低 16 位 = 寫狀態 (獨佔鎖計數 + 重入計數)
  • 讀鎖:共享鎖,多線程可同時持有
  • 寫鎖:獨佔鎖,排斥所有讀/寫
  • 鎖降級:寫鎖 → 讀鎖 → 釋放寫鎖 (支持);鎖升級:不支持

公平性策略

特性說明
可重入同一線程可多次獲取同一鎖,計數器機制
互斥同一時刻僅一線程持有鎖
自動釋放正常退出/異常拋出均自動釋放,無死鎖風險
內存可見性釋放鎖前刷新到主存,獲取鎖後從主存讀取
非公平搶鎖機制,不保證等待隊列順序
模式策略特點
非公平讀線程可插隊獲取讀鎖高吞吐,寫線程可能餓死
公平隊首若為寫線程,後續讀線程阻塞等待寫線程無餓死,延遲略高

AQS:鎖框架基石

核心組件

AbstractQueuedSynchronizer
├── state (volatile int)           // 同步狀態
├── head/tail (Node)               // CLH 隊列頭尾
├── acquire/release (模板方法)     // 獲取/釋放邏輯
├── tryAcquire/tryRelease (模板)   // 子類實現具體語義
└── ConditionObject                // 條件隊列

自定義鎖範例:雙工鎖 (允許 2 線程並發)

public class TwoPermitLock extends AbstractQueuedSynchronizer {
    @Override
    protected int tryAcquireShared(int arg) {
        for (;;) {
            int current = getState();
            int next = current + 1;
            if (next > 2) return -1; // 超過許可數
            if (compareAndSetState(current, next)) return 1;
        }
    }
    
    @Override
    protected boolean tryReleaseShared(int arg) {
        for (;;) {
            int current = getState();
            int next = current - 1;
            if (compareAndSetState(current, next)) return true;
        }
    }
    
    public void lock() { acquireShared(1); }
    public void unlock() { releaseShared(1); }
}

進階鎖:StampedLock (Java 8+)

StampedLock lock = new StampedLock();

// 樂觀讀 (極高性能,無鎖)
long stamp = lock.tryOptimisticRead();
Data data = readData();
if (!lock.validate(stamp)) { // 驗證期間無寫入
    // 回退到悲觀讀
    stamp = lock.readLock();
    try { data = readData(); } finally { lock.unlockRead(stamp); }
}

// 悲觀讀
stamp = lock.readLock();
try { ... } finally { lock.unlockRead(stamp); }

// 寫鎖
stamp = lock.writeLock();
try { ... } finally { lock.unlockWrite(stamp); }

// 讀鎖轉寫鎖 (慎用)
stamp = lock.readLock();
try {
    long writeStamp = lock.tryConvertToWriteLock(stamp);
    if (writeStamp != 0) {
        stamp = writeStamp;
        // 寫操作
    } else {
        lock.unlockRead(stamp);
        stamp = lock.writeLock();
        // 寫操作
    }
} finally { lock.unlock(stamp); }

選型決策指南

場景推薦理由
簡單同步、代碼塊短synchronizedJVM 優化、可讀性高、無釋放風險
需要可中斷、超時、公平性ReentrantLock功能豐富、顯式控制
讀多寫少、高併發讀ReentrantReadWriteLock共享讀鎖、大幅提升讀吞吐
讀多寫少、極致讀性能StampedLock樂觀讀無鎖、Java 8+
信號量控制並發度Semaphore許可證模型、限流
線程協作等待CountDownLatch/CyclicBarrier計數器/柵欄語義

避坑指南

  • ❌ `synchronized` 鎖住字符串常量 / 基本類型包裝對象 (常量池導致意外共享)
  • ❌ `lock.lock()` 後無 `try-finally` 包裹 `unlock()`
  • ❌ `ReentrantReadWriteLock` 寫鎖期間嘗試獲取讀鎖 (死鎖)
  • ❌ 長時間持有鎖執行 I/O、RPC、睡眠
  • ✅ 使用 `tryLock(timeout)` 避免死鎖
  • ✅ 縮小鎖粒度:分段鎖、CAS、ThreadLocal、無鎖數據結構
  • ✅ 考慮 `CompletableFuture`、Actor 模型替代傳統鎖

總結

理解鎖的本質是「協調共享狀態訪問」。從 `synchronized` 到 `Lock` 再到 `AQS`,是從隱式到顯式、從固定到可擴展的演進。實戰中:**優先用高層抽象 (並發集合、CompletableFuture)、再考慮顯式鎖、最後才是 synchronized**,並在架構層面通過無狀態設計、消息傳遞減少共享狀態。

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