ReentrantLock原始碼詳細解讀

2021-09-24 12:14:13 字數 4613 閱讀 7615

reentrantlock是面試中的高頻考點,其中實現原理還是很有必要了解的。它與synchronized類似,都是互斥鎖,但具有更好的擴充套件性。reentrantlock是基於aqs實現的,遺忘的同學可以回顧一下aqs原始碼詳細解讀。

1.1 繼承關係概述

首先看一下繼承關係圖,對它整體的構造有乙個初步的認識。

reentrantlock內部類繼承樹

我們發現,reentrantlock實現了公平鎖和非公平鎖。都通過他們的父類sync來排程。

1.2 reentrantlock中的重要方法

構造方法:無參構造方法,預設建立非公平鎖;有參構造方法,並且fair==true時,建立公平鎖。

//維護了乙個sync,對於鎖的操作都交給sync來處理

private final sync sync;

public reentrantlock()

public reentrantlock(boolean fair)

獲取資源資源(鎖)的方法:可以看出,請求都是交給sync來排程的。

//請求鎖資源,會阻塞且不處理中斷請求,

//沒有呼叫unlock(),則會一直被阻塞。

public void lock()

//執行緒在請求lock並被阻塞時,如果被interrupt,則此執行緒會被喚醒並被要求處理

public void lockinterruptibly() throws interruptedexception

//嘗試獲取鎖,預設獲取的是非公平鎖,失敗後不會阻塞

//直接返回true或false

public boolean trylock()

//過載方法,在規定時間內獲取鎖,獲取不到則返回false

public boolean trylock(long timeout, timeunit unit)

throws interruptedexception

釋放資源(鎖)的方法:不管是公平還是非公平鎖,都會呼叫aqs.release(1),給當前執行緒持有鎖的數量-1。

public void unlock()
我們主要看非公平鎖與公平鎖獲取資源的方法,因為釋放資源的邏輯是一樣的。

2.1 sync獲取資源

sync中定義了獲取資源的總入口。具體的呼叫還是看實現類是什麼。

abstract void lock();
2.2 非公平鎖獲取資源

final void lock()
tryacquire():走的是sync.nofairtryacquire()。

protected final boolean tryacquire(int acquires)
nonfairtryacquire(int acquires):如果鎖空閒,則用cas修改state;如果鎖被占用,則判斷佔有者是不是自己,實現可重入。最終沒有獲取鎖到就返回false。

final boolean nonfairtryacquire(int acquires) 

}//獲取失敗則先判斷當前執行緒是否是鎖的持有者

//這裡很重要,也是reentrantlock實現可重入的原因

else if (current == getexclusiveownerthread())

return false;

}

2.3 公平鎖獲取資源lock():也是阻塞的。與非公平鎖的區別是,不能直接通過cas修改state,而是直接走aqs.acquire()。

final void lock()
tryaquire():與非公平鎖類似,aqs.acquire()會呼叫這個鉤子方法。只不過多判斷了hasqueuedpredecessors(),判斷當前節點在等待佇列中是否有前驅節點,如果有,則說明有執行緒比當前執行緒更早的請求資源,根據公平性,當前執行緒請求資源失敗;如果當前節點沒有前驅節點,才有做後面的邏輯判斷的必要性。

protected final boolean tryacquire(int acquires) 

}//實現可重入

else if (current == getexclusiveownerthread())

return false;

}

reentrantlock有3中獲取鎖的方法,lock(),trylock(),lockinterruptibly()。

3.1 trylock()--嘗試獲取資源

trylock():走的還是sync的方法,在指定時間內獲取鎖,直接返回結果。

public boolean trylock(long timeout, timeunit unit)

throws interruptedexception

tryacquirenanos():如果呼叫trylock的規定時間內嘗試方法,就會呼叫該方法,先判斷是否中斷,然後嘗試獲取資源,否則進入aqs.doacquirenanos()(這個方法在上篇文章有解釋)。在規定時間內自旋拿資源,拿不到則掛起再判斷是否被中斷。

public final boolean tryacquirenanos(int arg, long nanostimeout)

throws interruptedexception

3.2 lockinterruptibly()--獲取鎖時響應中斷lockinterruptibly():交給了排程者sync執行。

public void lockinterruptibly() throws interruptedexception
acquireinterruptibly():當嘗試獲取鎖失敗後,就進行阻塞可中斷的獲取鎖的過程。呼叫aqs.doacquireinterruptibly()(這個方法在上篇文章也有詳細解釋)。

public final void acquireinterruptibly(int arg)

throws interruptedexception

公平鎖與非公平鎖的釋放都是一樣的。通過前面的閱讀,可以知道,reentrantlock.release()呼叫的是sync.release(1)。本質還是進入aqs.release(1),下面看看其中的tryrelease()這個鉤子方法如何實現。

sync釋放資源

tryrelease():嘗試釋放鎖,徹底釋放後返回true。

protected final boolean tryrelease(int releases) 

//設定釋放後的state

setstate(c);

return free;

}

1)reentrantlock是如何實現可重入的?

不管是公平鎖還是非公平鎖,在獲取鎖時呼叫的tryacquire()方法,獲取成功後會setexclusiveownerthread(current)。將本執行緒設定為主人,之後每次呼叫tryacquire()時,發現當前執行緒就是主人,直接返回true。

2)簡述公平鎖與非公平鎖的區別?

獲取鎖的順序與請求鎖的時間順序一致就是公平鎖,反之則為非公平鎖。公平鎖每次都是從同步佇列中的第乙個節點獲取到鎖,而非公平性鎖則不一定,有可能剛釋放鎖的執行緒能再次獲取到鎖。

公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低效能開銷。因此,reentrantlock預設選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。

3)aqs中有哪些資源訪問模式?區別?

獨佔模式和共享模式。

只有乙個執行緒才能持有這個鎖就是獨佔模式,由node節點中的nextwait來標識。

reentrantlock就是乙個獨佔鎖;而writeandreadlock的讀鎖則能由多個執行緒同時獲取,但它的寫鎖則只能由乙個執行緒持有,因此它使用了兩種模式。

4)為什麼reentrantlock.lock()方法不能被其他執行緒中斷?

因為當前執行緒前面可能還有等待執行緒,在aqs.acquirequeued()的迴圈裡,執行緒會再次被阻塞。parkandcheckinterrupt()返回的是thread.interrupted(),不僅返回中斷狀態,還會清除中斷狀態,保證阻塞執行緒忽略中斷。

其實看完aqs原始碼後,reentrantlock就是個弟弟。在實現上其實並不複雜,實現了aqs的獨佔模式。希望看完之後能對可重入鎖,響應中斷鎖有更深入的理解。

AbstractCollection原始碼分析

abstractcollection抽象類提供了collection的骨架實現,collection分析請看 這裡直接看它的 是如何實現的.public abstract iterator iterator 該方法沒有實現.public abstract int size 該方法沒有實現.publi...

ThreadPoolExecutor原始碼閱讀

執行緒池解決兩個問題 一是復用執行緒,減少建立銷毀執行緒帶來系統開銷 二是限定系統資源使用邊界,避免大量執行緒消耗盡系統記憶體 適用於互不依賴,執行時間短,不需要對執行緒控制操作的執行緒 新增任務時,1.若執行緒數量小於corepoolsize,則新增執行緒執行任務 2.若執行緒數量大於等於core...

OrangePi One Android 原始碼編譯

一 系統環境搭建參照 二 lichee原始碼編譯 1.檢視help build.sh h2.配置核心 cd linux 3.4 make arch arm menuconfig 進入配置頁面,上下移動列表,空格是選擇列表,左右移動選擇退出選項 3.首次編譯執行清除 在 lichee linux3.4...