首頁 > 科技

通俗易懂的ReentrantLock

2021-06-18 05:59:05

前言

自己開的坑,跪著也要填完,歡迎來到Java併發程式設計系列第五篇ReentrantLock,文章風格依然是圖文並茂,通俗易懂,本文帶讀者們深入理解ReentrantLock設計思想。

認識下ReentrantLock

先帶讀者們和ReentrantLock見個面,簡單的認識下什麼是ReentrantLock

ReentrantLock是可重入的互斥鎖,雖然具有與synchronized相同功能,但是會比synchronized更加靈活(具有更多的方法)。

ReentrantLock底層基於AbstractQueuedSynchronizer實現,AbstractQueuedSynchronizer在前一篇已經詳細解剖過了,本文不做過多描述,但是會簡單的介紹下,照顧小白。

AbstractQueuedSynchronizer抽象類定義了一套多執行緒訪問共享資源的同步模板,解決了實現同步器時涉及的大量細節問題,能夠極大地減少實現工作,用大白話來說,AbstractQueuedSynchronizer為加鎖和解鎖過程提供了統一的模板函數,只有少量細節由子類自己決定。

經過上述介紹,相信讀者們對ReentrantLock有了初步的印象,下面開始發車了~

ReentrantLock結構組成

學任何知識的第一件事,就是看清它的全貌,梳理出整體結構與主流程,之後逐個擊破,所以帶讀者們先看下ReentrantLock整體結構組成,對它的實現有個大致的瞭解。

上圖可以看出來,ReentrantLock整體結構還是非常簡單,給讀者們分析一波,為什麼ReentrantLock結構是這樣設計的,首先ReentrantLock實現了Lock介面,Lock介面是Java中對鎖操作行為的統一規範,遵守規則規範是守法公民的基本素養,合情合理,Lock介面的定義如下

Lock介面定義的函數不多,接下來ReentrantLock要去實現這些函數,遵循著解耦可擴展設計,ReentrantLock內部定義了專門的元件SyncSync繼承AbstractQueuedSynchronizer提供釋放資源的實現,NonfairSyncFairSync是基於Sync擴展的子類,即ReentrantLock的非公平模式與公平模式,它們作為Lock介面功能的基本實現。

大白話來說,企業的老闆,為了響應政府的政策,需要對企業內部做調整,但是政府每年政策都不一樣,每次都要自己去親力親為,索性長痛不如短痛,專門成立一個政策應對部門,以後這些事情都交予這個部門去做,老闆只需要指揮它們就好了。

ReentrantLock結構組成讀者們也清楚了,下面我只需對Sync、NonfairSync、FairSync逐個擊破,ReentrantLock自然水到渠成。

小貼士:在ReentrantLock中,它對AbstractQueuedSynchronizerstate狀態值定義為執行緒獲取該鎖的重入次數,state狀態值為0表示當前沒有被任何執行緒持有,state狀態值為1表示被其他執行緒持有,因為支援可重入,如果是持有鎖的執行緒,再次獲取同一把鎖,直接成功,並且state狀態值+1,執行緒釋放鎖state狀態值-1,同理重入多次鎖的執行緒,需要釋放相應的次數。

Sync

Sync可以說是ReentrantLock的親兒子,它寄託了全村的希望,完美的繼承了AbstractQueuedSynchronizer,是ReentrantLock的核心,後面的NonfairSyncFairSync都是基於Sync擴展出來的子類。

聽我吹完了Sync,下面就來看看Sync類定義的核心部分

我發現Sync有點偏心,首先Sync實現釋放資源的細節(A Q S留給子類實現的tryRelease),然後聲明瞭獲取鎖的抽象函數(lock),子類根據業務實現,目前看來還是很公平,但是Sync還定義了一個nonfairTryAcquire函數,這個函數是專門給NonfairSync使用的,FairSync卻沒有這種待遇,所以說Sync偏心。

Sync邏輯都比較簡單,實現了A Q S類的釋放資源(tryRelease),然後抽象了一個獲取鎖的函數讓子類自行實現(lock),再加一個偏心的函數nonfairTryAcquire,但是再怎麼簡單,圖還是要有的,這是我讀者們的福利。

下面放一張tryRelease流程圖,在後續的NonfairSync、FairSync都會有全面的流程。

NonfairSync

現在我們把視線轉移到NonfairSync,在ReentrantLock中支援兩種獲取鎖的策略,分別是非公平策略與公平策略,NonfairSync就是非公平策略。

此時讀者會有問道,什麼是非公平策略?

在說非公平策略前,先簡單的說下A Q S(AbstractQueuedSynchronizer)流程,A Q S為加鎖和解鎖過程提供了統一的模板函數,加鎖與解鎖的模板流程是,獲取鎖失敗的執行緒,會進入CLH佇列阻塞,其他執行緒解鎖會喚醒CLH佇列執行緒,如下圖所示(簡化流程)

上圖中,執行緒釋放鎖時,會喚醒CLH佇列阻塞的執行緒,重新競爭鎖,要注意,此時可能還有非CLH佇列的執行緒參與競爭,所以非公平就體現在這裡,非CLH佇列執行緒與CLH佇列執行緒競爭,各憑本事,不會因為你是CLH佇列的執行緒,排了很久的隊,就把鎖讓給你。

瞭解了什麼是非公平策略,我們再來看看NonfairSync類定義

NonfairSync繼承Sync實現了lock函數,lock函數也非常簡單,C A S設定狀態值state1代表獲取鎖成功,否則執行A Q Sacquire函數(獲取鎖模板),另外NonfairSync還實現了A Q S留給子類實現的tryAcquire函數(獲取資源),這個被Sync寵幸的幸運兒,直接使用Sync提供的nonfairTryAcquire函數來實現tryAcquire,最後子類實現的tryAcquire函數在A Q Sacquire函數中被使用。

是不是有點繞?沒事帶大家一起縷一縷

首先A Q Sacquire函數是獲取鎖的流程模板,模板流程會先執行tryAcquire函數獲取資源,tryAcquire函數要子類實現,NonfairSync作為子類,實現了tryAcquire函數,具體實現是呼叫了SyncnonfairTryAcquire函數。

接下來,我們再看看Sync專門給NonfairSync準備的nonfairTryAcquire函數邏輯

對上述程式碼邏輯做個簡單的概括,當前執行緒檢視資源是否可獲取:

  • 可獲取,嘗試使用C A S設定state1C A S成功代表獲取資源成功,否則獲取資源失敗

  • 不可獲取,判斷當執行緒是不是持有鎖的執行緒,如果是,state重入計數,獲取資源成功,否則獲取資源失敗

就兩句話,是不是十分簡單,雖然簡單但我還是畫了一張nonfairTryAcquire流程圖給讀者們觀賞

FairSync

有非公平策略,就有公平策略,FairSync就是ReentrantLock的公平策略。

所謂公平策略就是,嚴格按照CLH佇列順序獲取鎖,執行緒釋放鎖時,會喚醒CLH佇列阻塞的執行緒,重新競爭鎖,要注意,此時可能還有非CLH佇列的執行緒參與競爭,為了保證公平,一定會讓CLH佇列執行緒競爭成功,如果非CLH佇列執行緒一直佔用時間片,那就一直失敗(構建成節點插入到CLH隊尾,由A S Q模板流程執行),直到時間片輪到CLH佇列執行緒為止,所以公平策略的效能會更差。

瞭解了什麼是公平策略,我們再來看看FairSync類定義

其實我們不難發現FairSync流程與NonfairSync基本一致,唯一的區別就是在C A S執行前,多了一步hasQueuedPredecessors函數,這一步就是判斷當前執行緒是不是CLH佇列被喚醒的執行緒,如果是就執行C A S,否則獲取資源失敗,下面水一張圖

Lock的實現

最後帶大家看看ReentrantLock中是如何實現Lock的,先看構造器部分

ReentrantLock預設是使用非公平策略,如果想指定模式,可以通過入參fair來選擇,這裡就不做過多概述,接下來看看ReentrantLockLock的實現

是不是特別簡單,ReentrantLockLock的實現都是基於Sync來做的,有一種神器在手,天下我有的風範。

Sync承包了所有事情,為何它如此牛皮,因為Sync上有AbstractQueuedSynchronizer老大哥罩著,下有NonfairSyncFairSync兩小弟可差遣,所以成為ReentrantLock的利器也合情合理。

最後畫一張結合A Q S的流程圖,來結束ReentrantLock

嘮叨嘮叨

不知道各位讀者喜不喜歡,如果喜歡的話請點贊、再看,讓我感受下讀者們的熱情,最後下面會有一個關於閱讀時間的調查,也希望讀者們可以熱情參與,好讓我對發文時間心裡有個B樹。

想了解更多精彩內容,快來關注計算機java程式設計


IT145.com E-mail:sddin#qq.com