深入分析 Java Lock 同步鎖

  • 2020 年 12 月 8 日
  • 筆記

前言

Java 的鎖實現,有 Synchronized 和 Lock。上一篇文章深入分析了 Synchronized 的實現原理:由Java 15廢棄偏向鎖,談談Java Synchronized 的鎖機制

本篇文章深入分析 Lock 的實現,以及對比其與 Synchronized 的不同。

Synchronized 與 Lock 的對比

  • 實現方式:Synchronized 由 JVM 實現;Lock 由 Java 底層程式碼實現
  • 鎖獲取:Synchronized 是 JVM 隱式獲取,不用 Java 程式碼;Lock 由 Java 程式碼實現,有多種獲取方式
  • 鎖的釋放:Synchronized 是 JVM 隱式釋放,不用 Java 程式碼;Lock 可通過 Lock.unlock(),在 finally 中釋放
  • 鎖的類型:Synchronized 是非公平、可重入的Lock 是非公平性、公平性、可重入的
  • 鎖的中斷:Synchronized 不支援中斷,Lock 支援中斷

實現原理

Lock 是一個介面類,其介面方法定義:

  • lock():獲取鎖
  • lockInterruptibly():如果當前執行緒未被中斷,則獲取鎖,可以響應中斷
  • tryLock():僅在調用時鎖為空閑狀態才獲取該鎖,可以響應中斷
  • tryLock(long time, TimeUnit unit):如果鎖在給定的等待時間內空閑,並且當前執行緒未被中斷,則獲取鎖
  • unlock():釋放鎖
  • newCondition():返回綁定到此 Lock 實例的新 Condition 實例

基礎原理就不贅述了,Lock 介面的常用類:

  • ReentrantLock(重入鎖)
  • ReentrantReadWriteLock(可重入的讀寫鎖)

其都是依賴 AQS 實現的。AQS 類結構中包含一個基於鏈表實現的等待隊列(CLH 隊列),用於存儲所有阻塞的執行緒,AQS 中還有一個 state 變數,表示加鎖狀態。

ReentrantLock

ReentrantReadWriteLock

ReentrantLock 是獨佔鎖,對於同一份數據,如果一個執行緒讀數據,另一個執行緒在寫數據,那麼讀到的數據與最終的數據可能不一致。

在實際的業務場景中,讀操作遠遠大於寫操作。在多執行緒編程中,讀操作不會修改共享資源的數據。針對讀多寫少的場景,我們可以使用 ReentrantReadWriteLock 來優化,ReentrantReadWriteLock 內部維護了 2 個鎖:讀鎖 ReadLock,寫鎖 WriteLock

規則簡單概括為:

  • 如果寫鎖沒有被佔用,就可以獲取讀鎖
  • 如果讀鎖沒有被佔用,才可以獲取寫鎖

下面的測試程式碼,因為讀鎖和寫鎖是同時 lock 的,所以會死鎖。

@Test
public void testReadWriteLock() {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    lock.readLock().lock();
    lock.writeLock().lock();
    System.out.println("Hello");
    lock.readLock().unlock();
    lock.writeLock().unlock();
}

StampedLock

上述 ReentrantReadWriteLock 會有一個問題:在讀很多,寫很少的情況下,執行緒會因一直無法獲取到鎖而處於等待狀態。

在 JDK 1.8 中,Java 提供了 StampedLock,有三種模式:

  • 悲觀讀
  • 樂觀讀

一個寫執行緒獲取寫鎖,通過 WriteLock 獲取票據 stamp,WriteLock 是一個獨佔鎖,unlockWrite 需要傳遞參數 stamp。

不一樣的地方在於讀過程。執行緒會先通過樂觀鎖 tryOptimisticRead 獲取票據 stamp,如果當前沒有執行緒持有寫鎖,則會返回一非 0 的 stamp 資訊。執行緒獲取該 stamp 後,會拷貝一份共享資源到房發展。

之後方法還需要調用 validate,驗證之前調用 tryOptimisticRead 返回的 stamp 在當前是否有其它執行緒持有了寫鎖,如果是,那麼 validate 會返回 0,升級為悲觀鎖;否則就可以使用該 stamp 版本的鎖對數據進行操作。

總結

相比於 Synchronized 同步鎖,Lock 實現的鎖更加靈活:

  • 可以分為讀寫鎖,優化讀大於寫的場景
  • 可以中斷
  • 可以超時
  • 可以區分公平性

公眾號

coding 筆記、點滴記錄,以後的文章也會同步到公眾號(Coding Insight)中,希望大家關注_

程式碼和思維導圖在 GitHub 項目中,歡迎大家 star!