1. 程式人生 > >漫畫圖解java可重入鎖(ReentrantLock)的實現原理

漫畫圖解java可重入鎖(ReentrantLock)的實現原理

前言

相信學過java的人都知道 synchronized 這個關鍵詞,也知道它用於控制多執行緒對併發資源的安全訪問,興許,你還用過Lock相關的功能,但你可能從來沒有想過java中的鎖底層的機制是怎麼實現的。如果真是這樣,而且你有興趣瞭解,今天我將帶領你輕鬆的學習下java中非常重要,也非常基礎的可重入鎖-ReentrantLock的實現機制。

聽故事把知識掌握了

在一個村子裡面,有一口井水,水質非常的好,村民們都想打井裡的水。這井只有一口,村裡的人那麼多,所以得出個打水的規則才行。村長絞盡腦汁,最終想出了一個比較合理的方案,咱們來仔細的看看聰明的村長大人的智慧。

井邊安排一個看井人,維護打水的秩序。

打水時,以家庭為單位,哪個家庭任何人先到井邊,就可以先打水,而且如果一個家庭佔到了打水權,其家人這時候過來打水不用排隊。而那些沒有搶佔到打水權的人,一個一個挨著在井邊排成一隊,先到的排在前面。打水示意圖如下 :

打水示意圖

是不是感覺很和諧,如果打水的人打完了,他會跟看井人報告,看井人會讓第二個人接著打水。這樣大家總都能夠打到水。是不是看起來挺公平的,先到的人先打水,當然不是絕對公平的,自己看看下面這個場景 :

同家人一起打水

看著,一個有娃的父親正在打水,他的娃也到井邊了,所以女憑父貴直接排到最前面打水,羨煞旁人了。 
以上這個故事模型就是所謂的公平鎖模型,當一個人想到井邊打水,而現在打水的人又不是自家人,這時候就得乖乖在佇列後面排隊。

事情總不是那麼一帆風順的,總會有些人想走捷徑,話說看井人年紀大了,有時候,眼力不是很好,這時候,人們開始打起了新主意。新來打水的人,他們看到有人排隊打水的時候,他們不會那麼乖巧的就排到最後面去排隊,反之,他們會看看現在有沒有人正在打水,如果有人在打水,沒輒了,只好排到佇列最後面,但如果這時候前面打水的人剛剛打完水,正在交接中,排在隊頭的人還沒有完成交接工作,這時候,新來的人可以嘗試搶打水權,如果搶到了,呵呵,其他人也只能睜一隻眼閉一隻眼,因為大家都預設這個規則了。這就是所謂的非公平鎖模型。新來的人不一定總得乖乖排隊,這也就造成了原來佇列中排隊的人可能要等很久很久。 
 

java可重入鎖-ReentrantLock實現細節

ReentrantLock支援兩種獲取鎖的方式,一種是公平模型,一種是非公平模型。在繼續之前,咱們先把故事元素轉換為程式元素。

元素轉換 
 

咱們先來說說公平鎖模型:

初始化時, state=0,表示無人搶佔了打水權。這時候,村民A來打水(A執行緒請求鎖),佔了打水權,把state+1,如下所示:

執行緒A獲取鎖

執行緒A取得了鎖,把 state原子性+1,這時候state被改為1,A執行緒繼續執行其他任務,然後來了村民B也想打水(執行緒B請求鎖),執行緒B無法獲取鎖,生成節點進行排隊,如下圖所示:

執行緒B等待

初始化的時候,會生成一個空的頭節點,然後才是B執行緒節點,這時候,如果執行緒A又請求鎖,是否需要排隊?答案當然是否定的,否則就直接死鎖了。當A再次請求鎖,就相當於是打水期間,同一家人也來打水了,是有特權的,這時候的狀態如下圖所示:

可重入鎖獲取

到了這裡,相信大家應該明白了什麼是可重入鎖了吧。就是一個執行緒在獲取了鎖之後,再次去獲取了同一個鎖,這時候僅僅是把狀態值進行累加。如果執行緒A釋放了一次鎖,就成這樣了:

執行緒A釋放一次鎖

僅僅是把狀態值減了,只有執行緒A把此鎖全部釋放了,狀態值減到0了,其他執行緒才有機會獲取鎖。當A把鎖完全釋放後,state恢復為0,然後會通知佇列喚醒B執行緒節點,使B可以再次競爭鎖。當然,如果B執行緒後面還有C執行緒,C執行緒繼續休眠,除非B執行完了,通知了C執行緒。注意,當一個執行緒節點被喚醒然後取得了鎖,對應節點會從佇列中刪除。 
 

非公平鎖模型

如果你已經明白了前面講的公平鎖模型,那麼非公平鎖模型也就非常容易理解了。當執行緒A執行完之後,要喚醒執行緒B是需要時間的,而且執行緒B醒來後還要再次競爭鎖,所以如果在切換過程當中,來了一個執行緒C,那麼執行緒C是有可能獲取到鎖的,如果C獲取到了鎖,B就只能繼續乖乖休眠了。這裡就不再畫圖說明了。 
 

其它知識點

java5中添加了一個併發包, java.util.concurrent,裡面提供了各種併發的工具類,通過此工具包,可以在java當中實現功能非常強大的多執行緒併發操作。對於每個java攻城獅,我覺得非常有必要了解這個包的功能。雖然做不到一步到位,但慢慢虛心學習,沉下心來,總能慢慢領悟到java多執行緒程式設計的精華。 
 

結束語

可重入鎖的實現會涉及到CAS,AQS,java記憶體可見性(volatile)等知識,為了避免大家直接被程式碼搞暈,故而想以最簡單的方式把可重入鎖進行抽象,講明白其中的實現原理,這樣看起原始碼也有個借鑑的思路,希望本篇能夠幫助到你們。