java多線程之六——重入鎖ReentrantLock

本文基于java version "1.8.0_77"

ReentrantLock(java.util.concurrent.locks)(譯為:重入鎖)是java 5.0之后新加入的并發機制。他的出現并不是替代之前的內置鎖synchronized,而是在內置鎖不適用時,可以提供更加靈活的加鎖機制。

ReentrantLock使用:

Lock lock = new ReentrantLock();  //可選參數:boolean 是否公平

// lockInterruptibly(),可中斷的獲取鎖
// tryLock()//如果已經被lock,則立即返回false不會等待,達到忽略操作的效果   
// tryLock(long timeout, TimeUnit unit) //如果已經被lock,嘗試等待,看是否可以獲得鎖,如果超市仍然無法獲得鎖則返回false繼續執行  

lock.lock(); / /如果被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果  
try {   
    //do something
}  
finally {  
    lock.unlock();   // 不要漏掉
}  

ReentrantLock的使用很簡單,首尾加解鎖,中間是執行同步代碼。ReentrantLock同樣是一種互斥,可重入鎖,同時它支持了獲取鎖的時候的公平性與否。

  • 互斥:表示每次只有一個線程可以獲取鎖執行同步代碼;
  • 可重入:表示同一個線程可以對同一個鎖的重復獲取,比如在循環中加解鎖。
  • 公平性:等待中的線程是否按照時間順序獲取鎖。

ReentrantLock與synchronized

ReentrantLock與synchronized相同,都是可重入鎖,互斥鎖。在提供了與synchronized相同的鎖功能的同時,ReentrantLock還提供了如下功能:

  • 鎖等待可以設置超時時間 tryLock(long, TimeUnit),如果取得成功則繼續處理,取得不成功,可以等下次運行的時候處理,所以不容易產生死鎖。而synchronized則一旦進入鎖請求要么成功,要么一直阻塞,所以更容易產生死鎖。此方法僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值true。如果鎖不可用,則此方法將立即返回值false。
  • 可以設置可中斷的鎖等待 lockInterruptibly()
  • 可以設置鎖等待的公平性 ReentrantLock(boolean fair)

需要注意的地方:lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!

Lock

ReentrantLock實現了Lock接口,Lock接口定義了如下方法:

  • void lock() 獲取鎖,可等待
  • void lockInterruptibly() throws InterruptedException可中斷的獲取鎖
  • boolean tryLock();如果已經被lock,則立即返回false不會等待,達到忽略操作的效果
  • boolean tryLock(long, TimeUnit) throws InterruptedException;如果已經被lock,嘗試等待,看是否可以獲得鎖,如果超時仍然無法獲得鎖則返回false繼續執行
  • void unlock();解鎖
  • Condition newCondition() 譯為:“條件”,創建一個新的Condition,綁定到當前Lock上。Lock 替代了 synchronized 方法和語句的使用,而Condition 替代了 Object 監視器方法的使用。在Condition中,await()相當于wait(),signal()相當于notify(),signalAll()相當于notifyAll()。

Fuck源碼

我們從ReentrantLock調用的角度來看一下源碼:

看一下ReentrantLock的類結構

image.png

我們翻看ReentrantLock的源碼可以看到,ReentrantLock看起來更像是一個代理類,他的所有對外公布的方法全部都是有兩個內部類來實現的:FairSync(公平鎖)和NonfairSync(不公平鎖),對于是否為公平鎖,使用了策略模式,針對公平與否,使用不同的獲取鎖的策略。而這兩個類繼承了同一個類:Sync。而Sync類繼承了AbstractQueuedSynchronizer類。

我們在上篇的AQS源碼分析中講到,如果線程獲取鎖失敗,則進入等待隊列(FIFO),進入隊列是按照時間順序的。

AQS中有兩個重要部分:

  1. 等待隊列:是為了管理等待的線程;
  2. state系方法如下圖,由子類調用,用來控制是否允許獲取鎖。例如:ReentrantLock中用它來表示所有線程呢個已經重復獲取該鎖的次數,Semaphore用它來表示剩余的許可數量,FutureTask用它來表示任務的狀態(未開始,正在運行,已結束,已取消等)。這樣子類不用關心等待隊列如何工作,只需要控制state就可以實現一個lock。
image.png

非公平鎖&重入

NonfairSync:tryAcquire(int acquires)

image.png

看到非公平鎖的tryAcquire方法調用了nonfairTryAcquire方法,我們繼續往下看:

image.png

假設一個線程A嘗試獲取鎖,首先判斷當前state的值,

  • 如果為0,則表示當前并沒有線程獲取當前鎖,那就嘗試將state設置為1,如果失敗,則表示已經有線程獲取了鎖,線程A獲取失敗,返回false;如果成功,表示當前線程A獲取了鎖,并保存線程A,設置標記一下當前獨占模式的線程。
  • 如果不為0,表示已經有線程獲取過鎖。這時有兩種可能:有可能是線程A已經獲取過鎖,此時是線程A的重入;也有可能是其他線程(反正不是線程A)獲取鎖。這是需要先判斷一下是否是重入(if (current == getExclusiveOwnerThread())),如果是重入情況下,則繼續在state的值的基礎上+1,這時重入,再次獲取了鎖。

公平鎖&重入

首先,看公平鎖的lock源碼:
FairSync:tryAcquire()

image.png

可以看到,與上面講到的非公平鎖極其類似,唯一不同的是,在首次獲取鎖的時候,進行了hasQueuedPredecessors()判斷,判斷等待隊列中是否還有比當前線程更早的, 如果為空,或者當前線程線程是等待隊列的第一個時才占有鎖。我們看一下它的源碼:

image.png

這是對同步隊列中當前節點是否有前驅節點的判斷,如果該方法返回true,則表示有線程比當前線程更早地請求獲取鎖,因此需要等待前驅線程獲取并釋放鎖之后才能繼續獲取鎖。
這里判斷了(s = h.next) == null是為了,防止此時有另外的線程在同時搶占鎖,并獲取鎖,故如果為null,則進入等待隊列。

End

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容