深入分析 Java Lock 同步鎖
阿新 • • 發佈:2020-12-08
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201208175721699-2133379178.jpg)
# 前言
Java 的鎖實現,有 Synchronized 和 Lock。上一篇文章深入分析了 Synchronized 的實現原理:[由Java 15廢棄偏向鎖,談談Java Synchronized 的鎖機制](https://github.com/LjyYano/Thinking_in_Java_MindMapping/blob/master/2020-12-05%20%E7%94%B1Java%2015%E5%BA%9F%E5%BC%83%E5%81%8F%E5%90%91%E9%94%81%EF%BC%8C%E8%B0%88%E8%B0%88Java%20Synchronized%20%E7%9A%84%E9%94%81%E6%9C%BA%E5%88%B6.md)。
本篇文章深入分析 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 是一個介面類,其介面方法定義:
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201208175722109-1506875426.png)
- `lock()`:獲取鎖
- `lockInterruptibly()`:如果當前執行緒未被中斷,則獲取鎖,可以響應中斷
- `tryLock()`:僅在呼叫時鎖為空閒狀態才獲取該鎖,可以響應中斷
- `tryLock(long time, TimeUnit unit)`:如果鎖在給定的等待時間內空閒,並且當前執行緒未被中斷,則獲取鎖
- `unlock()`:釋放鎖
- `newCondition()`:返回繫結到此 Lock 例項的新 Condition 例項
基礎原理就不贅述了,Lock 介面的常用類:
- ReentrantLock(重入鎖)
- ReentrantReadWriteLock(可重入的讀寫鎖)
其都是依賴 `AQS` 實現的。AQS 類結構中包含一個`基於連結串列實現的等待佇列`(CLH 佇列),用於儲存所有阻塞的執行緒,AQS 中還有一個 state 變數,表示加鎖狀態。
# ReentrantLock
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201208175722968-1381528858.png)
# ReentrantReadWriteLock
ReentrantLock 是獨佔鎖,對於同一份資料,如果一個執行緒讀資料,另一個執行緒在寫資料,那麼讀到的資料與最終的資料可能不一致。
在實際的業務場景中,讀操作遠遠大於寫操作。在多執行緒程式設計中,讀操作不會修改共享資源的資料。針對讀多寫少的場景,我們可以使用 ReentrantReadWriteLock 來優化,ReentrantReadWriteLock 內部維護了 2 個鎖:讀鎖 `ReadLock`,寫鎖 `WriteLock`。
規則簡單概括為:
- 如果寫鎖沒有被佔用,就可以獲取讀鎖
- 如果讀鎖沒有被佔用,才可以獲取寫鎖
下面的測試程式碼,因為讀鎖和寫鎖是同時 lock 的,所以會死鎖。
```java
@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 專案](https://github.com/LjyYano/Thinking_in_Java_MindMapping)中,歡迎大家 star!
![](https://img2020.cnblogs.com/other/633265/202012/633265-20201208175723327-256274