天天看點

Java鎖之重入鎖

在講重入鎖之前,我們先看一段代碼

Java鎖之重入鎖

上述代碼想要實作的效果,就是使用兩個線程對i分别進行累加一百萬次,最終希望i的值是二百萬,如果按照上述代碼運作程式,你會發現i的值在絕大多數情況下都不能達到200萬,原因就是多線程的資料同步問題。

為了解決上述問題,我們自然而然想到synchronized關鍵字,通過對程式進行簡單改造,如下圖,紅框中的部分就是程式變動的部分:

Java鎖之重入鎖

在此處synchronized關鍵字的作用就是,當每個線程試圖對i進行++操作時,必須要先擷取o對象,一個o對象在同一時刻隻能被一個線程所持有,其他線程必須要等待持有o對象的線程進行i++操作并且釋放o對象之後去試圖擷取o對象,如果擷取成功線程繼續執行,如果擷取失敗,線程繼續等待。通過synchronized關鍵字會使原本并行化的操作變成順序執行,也就是說同一時刻,隻會有一個線程對i進行++,是以i最終的值必定會是200萬。

通過synchronized關鍵字可以實作多線程之間的同步控制,除了上述方法,Java為我們提供了很多并發控制的工具類,今天主要講的就是Java中的重入鎖ReentrantLock,效果基本等同于synchronized關鍵字。

使用重入鎖必須擷取一個重入鎖對象,通過new一個ReentrantLock即可獲得一個重入鎖對象。

Java鎖之重入鎖

使用重入鎖必須明确指定加鎖和解鎖操作,增強程式的可讀性。

Java鎖之重入鎖

同一把重入鎖隻能在同一時刻隻能被同一個線程鎖持有,也就是說,當線程1通過lock方法擷取鎖成功之後,其他線程如果想要獲得鎖必須等待線程1通過unlock方法釋放鎖之後才能擷取成功。

重入鎖支援多次加鎖和多次解鎖操作,但是加鎖和解鎖的次數必須保持一緻,如果一個線程的加鎖次數大于解鎖次數,會使得目前線程一直占有這把重入鎖,其他線程永遠無法擷取鎖,進而産生饑餓現象,相反如果解鎖的次數大于加鎖次數,程式則會抛出IllegalMonitorStateException異常。

重入鎖提供中斷響應,就是在等待鎖的過程中可以取消對鎖的請求。

Java鎖之重入鎖

通過圖檔上的代碼,很輕松的就構造了一個死鎖現象,當lock值是1,線程會先試圖擷取重入鎖lock1,500ms之後再試圖擷取重入鎖lock2,相反如果lock值不是1,線程會先試圖擷取重入鎖lock2,500ms之後在試圖擷取重入鎖lock1,此時,我在主函數中新開兩個線程,設定lock的值一個為1,另一個為2;

Java鎖之重入鎖

此時運作程式,你會發現程式永遠不會結束,原因就是兩個線程之間形成了死鎖現象。

細心的讀者或許已經發現,我在擷取重入鎖的時候不是使用lock()方法,而是使用的lockInterruptibly()方法,通過方法名稱也可以看出,lockInterruptibly()方法是支援中斷響應的。

下面我會在主線程通過t2.interrupt()中斷thread-2線程,這樣重入鎖2就會被釋放,進而使得thread-1可以正确執行完畢,但是thread-2隻是被中斷,無法正确執行完畢,隻會執行finally塊中的方法,最終程式的輸出結果如下圖:

Java鎖之重入鎖

除了通過中斷線程我們還可以通過鎖申請等待限時來避免死鎖和饑餓現象,所謂的鎖申請等待限時指的是申請鎖時指定一個最大等待時間,如果超過了等待時間還沒獲得鎖,線程就不再進行等待并且繼續執行。

擷取鎖時使用tryLock方法來擷取鎖就可以,此方法會有一個boolean傳回值,如果擷取鎖成功,傳回值為true,如果失敗,傳回值即為false。該方法有兩個重載方法,如下圖:

Java鎖之重入鎖

上述的實作方式都是非公平鎖,所謂的非公平就是,線程擷取鎖的成功率是随機的,有些鎖可能會一直成功擷取鎖,而有些線程會一直擷取不到鎖,而那些擷取不到鎖的線程就會一直處于等待狀态,進而産生饑餓現象。

為了解決上述問題,重入鎖支援多個線程之間以一種公平的方式來競争擷取鎖,通俗一點講比如有兩個線程,兩個線程試圖擷取同一把鎖,假如說第一次成功擷取鎖的是線程1,那麼下次成功擷取鎖的必定是線程2而不是線程1。

公平重入鎖的實作隻需要在擷取重入鎖時,構造參數中指定true。

Java鎖之重入鎖
Java鎖之重入鎖

上述代碼通過主線程中新開兩個線程,每個線程所做的事就是循環的擷取fairLock這把重入鎖,由于fairLock是一把公平的重入鎖,是以t1和t2兩個線程會交替獲得鎖,程式運作效果圖如下圖:

Java鎖之重入鎖

雖然公平的重入鎖可以避免死鎖的現象,但因内部必須要維護一個有序的線程隊列,是以公平鎖的實作成本較高,性能相對低下。

最後附上上述執行個體代碼的位址:https://github.com/Meikoheng/...