天天看點

Java并發之ReentrantLock詳解

        一、入題

        ReentrantLock是Java并發包中互斥鎖,它有公平鎖和非公平鎖兩種實作方式,以lock()為例,其使用方式為:

         那麼,ReentrantLock内部是如何實作鎖的呢?接下來我們就以JDK1.7中的ReentrantLock的lock()為例詳細研究下。

        二、ReentrantLock類的結構

        ReentrantLock類實作了Lock和java.io.Serializable接口,其内部有一個實作鎖功能的關鍵成員變量Sync類型的sync,定義如下:

        而這個Sync是繼承了AbstractQueuedSynchronizer的内部抽象類,主要由它負責實作鎖的功能。關于AbstractQueuedSynchronizer我們會在以後詳細介紹,你隻要知道它内部存在一個擷取鎖的等待隊列及其互斥鎖狀态下的int狀态位(0目前沒有線程持有該鎖、n存在某線程重入鎖n次)即可,該狀态位也可用于其它諸如共享鎖、信号量等功能。

        Sync在ReentrantLock中有兩種實作類:NonfairSync、FairSync,正好對應了ReentrantLock的非公平鎖、公平鎖兩大類型。

        三、擷取鎖主體流程

        ReentrantLock的鎖功能主要是通過繼承了AbstractQueuedSynchronizer的内部類Sync來實作的,其lock()擷取鎖的主要流程如下:

Java并發之ReentrantLock詳解

        首先,ReentrantLock的lock()方法會調用其内部成員變量sync的lock()方法;

        其次,sync的非公平鎖NonfairSync或公平鎖FairSync實作了父類AbstractQueuedSynchronizer的lock()方法,其會調用acquire()方法;

        然後,acquire()方法則在sync父類AbstractQueuedSynchronizer中實作,它隻有一段代碼:

      通過tryAcquire()方法試圖擷取鎖,擷取到直接傳回結果,否則通過嵌套調用acquireQueued()、addWaiter()方法将請求擷取鎖的線程加入等待隊列,如果成功的話,将目前請求線程阻塞,and,over!

         隊列如何實作及如何添加到隊列中以後再做詳細分析!這裡隻關注ReentrantLock的實作邏輯。

        上述就是公平鎖、非公平鎖實作擷取鎖的主要流程,而針對每種鎖來說,其實作方式有很大差别,主要就展現在各自實作類的lock()和tryAcquire()方法中。在sync的抽象類Sync及其抽象父類AbstractQueuedSynchronizer中,lock()方法和tryAcquire()方法被定義為抽象方法或者未實作,而是由具體子類去實作:

        下面,我們分别研究下非公平鎖和公平鎖的實作。

        四、非公平鎖NonfairSync

        1、lock()方法

        通過代碼可以看到,非公平鎖上來就無視等待隊列的存在而搶占鎖,通過基于CAS操作的compareAndSetState(0, 1)方法,試圖修改目前鎖的狀态,這個0表示AbstractQueuedSynchronizer内部的一種狀态,針對互斥鎖則是尚未有線程持有該鎖,而>=1則表示存線上程持有該鎖,并重入對應次數,這個上來就CAS的操作也是非公共鎖的一種展現,CAS操作成功的話,則将目前線程設定為該鎖的唯一擁有者。

        搶占不成功的話,則調用父類的acquire()方法,按照上面講的,繼而會調用tryAcquire()方法,這個方法也是由最終實作類NonfairSync實作的,如下:

        2、tryAcquire()

        而這個nonfairTryAcquire()方法實作如下:

        還是上來先判斷鎖的狀态,通過CAS來搶占,搶占成功,直接傳回true,如果鎖的持有者線程為目前線程的話,則通過累加狀态辨別重入次數。搶占不成功,或者鎖的本身持有者不是目前線程,則傳回false,繼而後續通過進入等待隊列的方式排隊擷取鎖。可以通過以下簡單的圖來了解:

Java并發之ReentrantLock詳解

        五、公平鎖FairSync

        1、lock()

        公平鎖的lock()方法就比較簡單了,直接調用acquire()方法,如下:

        公平鎖的tryAcquire()方法也相對較簡單,如下:

        目前線程會在得到目前鎖狀态為0,即沒有線程持有該鎖,并且通過!hasQueuedPredecessors()判斷目前等待隊列沒有前繼線程(也就是說,沒有比我優先級更高的線程在請求鎖了)擷取鎖的情況下,通過CAS搶占鎖,并設定自己為鎖的目前擁有者,當然,如果是重入的話,和非公平鎖處理一樣,通過累加狀态位标記重入次數。

        而一旦等待隊列中有等待者,或目前線程搶占鎖失敗,則它會乖乖的進入等待隊列排隊等待。公平鎖的實作大緻如下:

Java并發之ReentrantLock詳解

        六、預設實作

        ReentrantLock的預設實作為非公平鎖,如下:

        當然,你也可以通過另外一個構造方法指定鎖的實作方式,如下:

        七、其它

        即便是公平鎖,如果通過不帶逾時時間限制的tryLock()的方式擷取鎖的話,它也是不公平的,因為其内部調用的是sync.nonfairTryAcquire()方法,無論搶到與否,都會同步傳回。如下:

        但是帶有逾時時間限制的tryLock(long timeout, TimeUnit unit)方法則不一樣,還是會遵循公平或非公平的原則的,如下:

        其它流程都比較簡單,讀者可自行翻閱Java源碼檢視!