1. 程式人生 > >深入分析 Java Lock 同步鎖

深入分析 Java Lock 同步鎖

![](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