1. 程式人生 > 其它 >ReentrantLock鎖原始碼淺析

ReentrantLock鎖原始碼淺析

定義

公平鎖:

公平鎖是指多個執行緒按照申請鎖的順序來獲取鎖,執行緒直接進入佇列中排隊,佇列中的第一個執行緒才能獲得鎖

公平鎖的優點是等待鎖的執行緒不會餓死。

缺點是整體吞吐效率相對非公平鎖要低,等待佇列中除第一個執行緒以外的所有執行緒都會阻塞,CPU喚醒阻塞執行緒的開銷比非公平鎖大

公平鎖

非公平鎖是多個執行緒加鎖時直接嘗試獲取鎖,獲取不到才會到等待佇列的隊尾等待,但如果此時鎖剛好可用,那麼該執行緒可以無需阻塞直接獲取到鎖,所以非公平鎖有可能出現後申請鎖的執行緒先獲取鎖的場景

非公平鎖的優點是可以減少喚起執行緒的開銷,整體的吞吐效率高,因為執行緒有機率不阻塞直接獲得鎖,CPU不必喚醒所有執行緒

缺點是處於等待佇列中的執行緒可能會餓死,或者等很久才會獲得鎖

ReentrantLock是實現Lock介面的一種鎖

定義了一個final型別的Sync

Sync使用AQS的state表示對鎖的持有次數

分為公平鎖和非公平鎖

呼叫Lock方法

lock方法,分為公平鎖和非公平鎖兩個版本

(1)非公平鎖

(2)CAS操作

compareAndSetState(expect,update);

如果當前狀態值等於預期值,則原子地將同步狀態設定為給定的更新值。 此操作具有易失性讀寫的記憶體語義。

引數:期望 - 期望值,更新 - 新值

返回:如果成功則為真。 假返回表示實際值不等於預期值。

如果預期值位0那麼設定為1

最終也是呼叫sun.misc.Unsafe相關的方法,這個方法的四個引數第一個表示操作的是那個物件,第二個表示操作物件欄位的偏移量,第三個是期望值,第四個是更新值

(3)volatile

state狀態是用volatile來修飾的

如果不使用volatile的時候,如果兩個執行緒thread1和thread2同時執行同一個方法來修改state為1,當thread把state從0變為1時,thread2沒有感知還以為state還是0,這樣也成功把0修改為1,這樣兩個執行緒都認為自己成功執行了獲取鎖的行為

volatile的功能:

a: 保證變數線上程之間的可見性 就是上面說的

b:禁止指令重排序 在編譯階段插入記憶體屏障,來特定禁止指令重排序

如果使用volatile,兩個執行緒thread1和thread2,當thread1寫會成功之後會讓其它執行緒中該變數的副本失效,把成功後的值刷回主記憶體,並重新從主存load新的,這樣一來thread2 expect=0,update=1就會失敗,因為此時的expect=0是不成立的

JMM模型

工作記憶體:

每個執行緒都有自己的工作記憶體,裡面儲存了用到的變數和主記憶體的拷貝,叫做工作記憶體

執行緒對變數進行操作都在這個拷貝中操作,而不能直接讀寫主記憶體中的變數,每個執行緒的工作記憶體都是獨立的,執行緒操作資料只能在工作記憶體(虛擬機器棧)中進行,然後刷回到主存(堆加方法區)。這是 Java 記憶體模型定義的執行緒基本工作方式

當一個執行緒修改共享變數的值,其他執行緒能夠立即知道被修改了,Java是利用volatile關鍵字來提供可見性的。當變數被volatile修飾時,這個變數被修改後會立刻重新整理到主記憶體,當其它執行緒需要讀取該變數時,會去主記憶體中讀取新值。而普通變數則不能保證這一點

非公平鎖的流程

因此在非公平鎖中,AQS的volatile int state +1表示獲取到了鎖

通過CAS設定AQS的成員status,大家注意到status是用volatile來修飾的,它在此處表示讓所執行緒能夠獲取到最新更改的值

設定當前擁有獨佔訪問許可權的執行緒。

引數:Thread.curretThread()當前執行緒

執行緒 - 所有者執行緒

執行完lock方法後,呼叫tryAcquire方法

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

獲取當前執行緒,拿到當前鎖狀態

如果沒加鎖就加鎖,如果加了鎖,把當前鎖狀態state+1

若上述執行完成則代表成功,否則失敗

舉例分析

兩個執行緒thread1和thread2進入方法,通過CAS方法進行爭搶鎖,誰能成功設把state從0設定為1,誰就能夠把鎖的獨佔執行緒設為自己。獲取到鎖的執行緒退出方法,進入業務邏輯。

假設thread1獲取到鎖,在tryAcquire方法中,thread1如果又重入這把鎖,那麼會將state+1

未獲取到鎖的thread2會通過else方法進入acquire方法中。然後進入tryAcquire方法,thread2未爭搶到鎖的執行緒進入acquire(1),因為thread1持有鎖,那麼本次tryAcquire返回false,進入addWaiter方法,這個方法是有一個FIFO的雙向連結串列,進入連結串列後的執行緒是等待執行緒,waitStatus表示節點的狀態,裡面的結點入隊之後可以自旋獲取鎖,自旋如果成功會將結點頭從等待佇列中摘除,thread2獲取到鎖,thread2執行業務邏輯。否則thread2一直會嘗試獲取鎖,失敗了返回false,直到成功了就返回true

公平鎖

與非公平鎖的區別是,非公平鎖先通過CAS來搶佔鎖,然後在申請獲得鎖

而公平鎖直接申請獲得鎖

以獨佔模式獲取,忽略中斷。 通過至少呼叫一次 tryAcquire 實現,成功返回。 否則執行緒會排隊,可能會反覆阻塞和解除阻塞,呼叫 tryAcquire 直到成功。 此方法可用於實現方法 Lock.lock

它是ReentrantLock成員Sync的整個鎖的邏輯

tryAcquire方法是執行緒嘗試以獨佔模式獲取這個鎖。如果允許則獲取它。

acquireQueued方法是以獨佔不間斷模式獲取已在佇列中的執行緒,如果在等待時出現中斷,則會返回true,沒有中斷則返回false

如果執行緒無法獨佔模式獲取鎖,並且在等待時出現中斷,那麼中斷當前執行緒

公平鎖

首先獲得當前執行緒current

拿到狀態

公平鎖先判斷佇列(雙向連結串列)為空(head==tail)在進行cas搶佔,最終兩者為獲取鎖的執行緒都會進入到佇列中

因此,公平鎖就是保障了多執行緒下各執行緒獲取鎖的順序,先到的執行緒優先獲得鎖,後到的執行緒進入阻塞佇列