天天看點

java多線程源碼_Java多線程——ReentrantLock源碼閱讀

上一章《Java多線程—AQS架構源碼閱讀》講了AQS架構,這次講講它的應用類(注意不是子類實作,待會細講)。

ReentrantLock,顧名思義重入鎖,但什麼是重入,這個鎖到底是怎樣的,我們來看看類的注解說明

ReentrantLock與隐式鎖synchronized功能相同,但ReentrantLock更具有擴充性。

《鎖優化》裡提到Java在1.6對隐式鎖synchronized做了鎖的優化,使其性能與顯式鎖性能相差無異。是以在兩者的選擇上,更多的是考慮用法,以及功能上的擴充。

ReentrantLock是線程獨占的,不能與其他線程共享。所謂的重入,就是當本線程想再次獲得鎖,不需要重新申請,它本身就已經鎖了,即重入該鎖。

為什麼會允許鎖重入呢?因為該線程已經擁有鎖了,不會受其他線程幹擾,那麼裡面的共享變量就不會因為多線程執行造成線程不安全。相當于代碼已經在串行執行了,沒必要再申請多餘的鎖了,而是重入目前的鎖。

ReentrantLock會提供一個公平鎖的模式,如果選擇這個模式,會盡量使得擷取鎖是公平的,先來先得,但不一定嚴格按順序。

如果選擇了公平鎖,性能上會比不使用(預設)低一些。沒有一定保證順序,同時也降性能,是以如果沒有特别的要求,盡量使用預設的非公平鎖。

現在基于以上的認識,來看看ReentrantLock的基本實作吧。

ReentrantLock概覽

ReentrantLock是實作Lock接口的。是以主要的方法就是Lock接口定義的方法,包括lock()、tryLock()、unlock()、newCondition()等。

lock()與tryLock()的差別就是前者會一直等到直到擷取鎖,後者則是嘗試在當時擷取鎖,不會重複去申請擷取。

這個newCondition()感覺比較突兀,看起來完全不了解有什麼用,和Lock有什麼關系,我們後面再詳細了解。

ReentrantLock裡面有一個最核心的成員變量,sync。sync的類型就是内部類Sync。它是AQS的子類,也就是說它就是實作ReentrantLock同步的工具。而FairSync和unFairSync則是Sync的子類,封裝了是否公平的功能,用于指派給sync成員變量。

Sync同步器

Sync是繼承上文所介紹的AQS,是ReentrantLock裡面的NonfairSync和FairSync的父類。

看注解可以知道,Sync用了AQS的state(狀态原子值)來辨別鎖被持有的數量。

在AQS中,tryRelease()是沒有定義的,是以在Sync中重寫了。

先判斷下申請解鎖的線程是否獨占鎖的線程,否則抛出異常退出。

然計算新的state值,用目前state減去releases值。對于state值和releases值到底是多少,這裡可以先留個懸念,但大家可以思考下上面注解的定義也可以大概猜出來。

最後判斷新state值是否為0,為0則沒有線程占用,是以設目前獨占線程為空,并且更新state。這裡更新state值并不需要用CAS原子操作,因為隻有一個線程會占用這個鎖,不是這個線程都異常退出了。

AQS中核心的tryAcquire()方法并沒有在這裡實作,因為子類NonfaiSync和FairSync的實作并不一樣。但這裡同樣需要用到nonfairTryAcquire,是以抽象出來了。但為什麼同樣需要,暫時不得而知,帶着問題後面再看看。

先判斷目前鎖的state是否為0,為0則表示沒人擷取,然後通過CAS更新為acquires值(依然不知道值是多少),同時更新目前線程為鎖的獨占線程。

如果state不為0,則表示有線程已經占有了。但可能占有的線程是目前線程,那麼目前的state會加上acquires值。

這裡很容易就看出來state就是代表重入的次數!是以上面的謎題就解開了,releases,aquires都是代表每次申請的值,在ReentrantLock肯定都是1,他們的計算總值就是原子值state。

如果state不為0,也不是被目前線程占用,那麼傳回false擷取失敗。

NonfairSync

沒啥特别的,直接調用Sync的方法。也沒做修改。

FairSync

公平鎖的同步器。隻有當遞歸調用或者沒有其他等待者,再或者他自己本身排第一才能擷取鎖。

這話比較繞口,大概意思應該是不停地輪詢申請鎖,直到自己排到隊列的第一才能擷取。

乍看一看,這個方法基本和父類Sync的nonfairTryAcquire()一樣,唯一不同點就是在沒有線程占用的時候(state=0),多了個!hasQueuedPredecessors()前置判斷。

這個方法用來判斷是否隊列為空,或者目前線程是否在隊列的最前面。

是以公平鎖模式下,想要能擷取鎖,除非自己排在隊列的最前面。

綜上看,FairSync根本沒有調用到nonfairTryAcquire(),為何說子類都需要用到呢?繼續留着懸念,後面解答。

@ReservedStackAccess

可以看到上面介紹的tryAcquire()和tryRelease()都有@ReservedStackAccess。這個注解到底有什麼用?

查找了下資料,這個是JEP 270添加的新注解。它會保護被注解的方法,通過添加一些額外的空間,防止在多線程運作的時候出現棧溢出。具體看下圖

lock()、tryLock()成員函數

ReentrantLock裡面的lock()方法是調用成員變量sync的acquire()。

無論是否公平鎖都是直接調用AQS的acquire()方法,不過就是各自有tryAcuqire()的重寫,即上文所說的内容。

參數1,是透傳給tryAcquire()的,是以這裡代表是入鎖一次的意思。

而tryLock()調用的是成員變量sync的nonfairTryAcquire()。上文說到Sync内部類抽象了這個方法出來,說到子類都會用到,說的正是tryLock()方法需要用到。

是以顯而易見的,無論是否公平鎖,調用tryLock()都是用的非公平鎖的方法。為什麼呢?

因為tryLock()的try隻是嘗試,無論是否公平,對于方法來說沒有必要,隻是嘗試申請的時候能否擷取鎖而已。

至于其他成員函數,大都是圍繞擷取線程和隊列的狀态,沒什麼特别的,在這裡不再贅述,有興趣的可以看看源碼。

總結

回顧下要點ReentrantLock是一個可重入的鎖(被目前占用的線程重入)。

它有兩種模式公平與非公平,通過NonfairSync和FairSync指派sync成員變量實作。

兩種模式都是AQS的子類,通過重寫tryAcquire()差別不同。公平鎖多了是否在隊列的頭的判斷。

tryLock()方法沒有區分模式,都是一樣的。

上文提到的newCondition()還沒有涉及到,等後續再起一章節說下這個Condition。

如果覺得還不錯,請關注公衆号:Zack說碼