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

Java Lock 機制深度剖析:ReentrantLock、ReadWriteLock 與 AQS 核心原理

     編輯:Java 綜合教學

Java Lock 完全指南:從 synchronized 到 AQS 的進階之路

synchronized 雖簡單但功能受限(不可中斷、非公平、無條件等待)。JDK 5 引入 java.util.concurrent.locks.Lock 接口及實現類,提供可中斷、可超時、公平/非公平、多條件隊列、讀寫分離等高級特性,底層基於 AQS (AbstractQueuedSynchronizer) 框架構建。

Lock 接口核心方法

方法語義
lock()獲取鎖,阻塞直到成功
lockInterruptibly()可中斷獲取鎖,響應 Thread.interrupt()
tryLock()嘗試非阻塞獲取,立即返回成功/失敗
tryLock(timeout, unit)超時獲取,支援中斷
unlock()釋放鎖
newCondition()創建綁定該鎖的 Condition 對象(多條件隊列)

三大鎖特性對比

特性synchronizedReentrantLockReentrantReadWriteLock
可重入性✅✅✅ (讀寫分別可重入)
可中斷❌✅ (lockInterruptibly)✅
公平性❌ (非公平)✅ (可選公平/非公平)✅ (可選)
條件隊列1 個 (內置)多個 (newCondition)多個 (寫鎖擁有)
讀寫分離❌❌✅ (共享鎖/獨占鎖)
性能(低競爭)優 (JVM 優化、偏向鎖)略低讀多寫少極優

ReentrantLock 核心實現:AQS + CAS + CLH 隊列

關鍵數據結構

  • state (volatile int):同步狀態,0=未鎖定,>0=鎖定次數(可重入計數)
  • exclusiveOwnerThread:持有獨占鎖的線程
  • head/tail (volatile Node):CLH 變體雙向隊列,節點封裝線程及等待狀態
  • waitStatus:SIGNAL(-1) 需喚醒後繼、CANCELLED(1) 取消、CONDITION(-2) 條件等待

加鎖流程 (lock() → acquire(1))

  1. 快速路徑:CAS 將 state 從 0 改為 1,成功則設置 exclusiveOwnerThread 返回
  2. 可重入判斷:若當前線程已是持有者,state++ 直接返回
  3. 入隊阻塞:addWaiter(Node.EXCLUSIVE) 構建節點尾插入隊列
  4. 自旋獲取:acquireQueued 循環:前驅為 head 嘗試 CAS 獲取 → 失敗則 shouldParkAfterFailedAcquire 設置前驅 SIGNAL → LockSupport.park() 掛起 → 被喚醒/中斷後重試

釋放鎖 (unlock() → release(1))

  1. 檢查持有者合法性,state--,若歸零清除 exclusiveOwnerThread
  2. 若 state==0,喚醒 head 後繼節點 (unparkSuccessor)

公平 vs 非公平

  • 非公平 (默認):tryAcquire 直接 CAS 搶鎖,新來線程可插隊超越隊列頭
  • 公平:tryAcquire 先檢查 hasQueuedPredecessors(),有排隊線程則不搶鎖直接入隊

ReadWriteLock:讀寫分離提升併發度

核心規則

  • 讀鎖 (共享鎖):無寫鎖且無寫鎖請求時,多線程可同時持有
  • 寫鎖 (獨占鎖):無任何讀/寫鎖時,單線程可持有
  • 鍵升級:持有讀鎖不能直接獲取寫鎖(會死鎖),需先釋放讀鎖
  • 鍵降級:持有寫鎖 → 獲取讀鎖 → 釋放寫鎖(保證數據可見性)

狀態壓縮設計

state 32 位劃分:高 16 位 = 共享鎖計數 (讀鎖數,最大 65535),低 16 位 = 獨占鎖計數 (寫鎖重入數)。

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = 1 << SHARED_SHIFT; // 0x10000
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 0xFFFF

static int sharedCount(int c)  { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

ReadLock 獲取邏輯 (tryReadLock)

  1. 若有寫鎖且持有者非當前線程 → 失敗
  2. 讀鎖計數 < MAX_COUNT → CAS 增加共享計數 → 記錄首個讀者/當前讀者計數 (ThreadLocal HoldCounter)

WriteLock 獲取邏輯 (tryWriteLock)

  1. 若 state != 0 且 (寫鎖計數==0 或 持有者非當前線程) → 失敗
  2. 寫鎖計數 < MAX_COUNT → CAS 增加總狀態 → 設置持有者

Condition 實現:比 Object.wait/notify 更強大

  • 每把鎖可創建多個 Condition (如 notEmpty/notFull)
  • 支援 await()/signal()/signalAll()、可中斷 (awaitUninterruptibly)、超時 (awaitNanos)
  • 底層同樣使用 AQS 節點,waitStatus = CONDITION,單獨維護條件隊列,signal 時將節點轉移至同步隊列尾部

實戰選型建議

  1. 簡單同步、低競爭 → synchronized (JVM 優化最好、代碼簡潔)
  2. 需可中斷、超時、公平、多條件 → ReentrantLock
  3. 讀多寫少、數據結構併發訪問 (Map、List、緩存) → ReentrantReadWriteLock 或 StampedLock (JDK 8+ 樂觀讀)
  4. 高併發計數器、累加器 → LongAdder/AtomicLong (無鎖優於有鎖)

總結

Lock 體系以 AQS 為核心,通過 state、CLH 隊列、CAS、LockSupport.park/unpark 實現靈活的同步語義。深入理解 ReentrantLock 入隊/自旋/喚醒流程、ReadWriteLock 狀態壓縮與鎖升降級規則,是攻克高併發性能瓶頸、排查死鎖/活鎖的關鍵能力。

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