ReentrantLock基本介紹
ReentrantLock是一種可重入的互斥鎖,它具有與使用synchronized方法和語句所訪問的隱式監(jiān)視器鎖相同的一些基本行為和語義,但功能更強大。
ReentrantLock將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有。當(dāng)鎖沒有被另一個線程所擁有時,調(diào)用 lock 的線程將成功獲取該鎖并返回。如果當(dāng)前線程已經(jīng)擁有該鎖,此方法將立即返回??梢允褂胕sHeldByCurrentThread()和getHoldCount()方法來檢查此情況是否發(fā)生。
此類的構(gòu)造方法接受一個可選的公平參數(shù)。當(dāng)設(shè)置為 true 時(也是當(dāng)前ReentrantLock為公平鎖的情況),在多個線程的爭用下,這些鎖傾向于將訪問權(quán)授予等待時間最長的線程。否則此鎖將無法保證任何特定訪問順序。與采用默認(rèn)設(shè)置(使用不公平鎖)相比,使用公平鎖的程序在許多線程訪問時表現(xiàn)為很低的總體吞吐量(即速度很慢,常常極其慢),但是在獲得鎖和保證鎖分配的均衡性時差異較小。不過要注意的是,公平鎖不能保證線程調(diào)度的公平性。因此,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機會,這種情況發(fā)生在其他活動線程沒有被處理并且目前并未持有鎖時。還要注意的是,未定時的 tryLock 方法并沒有使用公平設(shè)置。因為即使其他線程正在等待,只要該鎖是可用的,此方法就可以獲得成功。
ReentrantLock 類基本結(jié)構(gòu)
通過上文的簡單介紹后,我相信很多小伙伴還是一臉懵逼,只知道上文我們提到了ReentrantLock與synchronized相比有相同的語義,同時其內(nèi)部分為了公平鎖與非公平鎖兩種鎖的類型,且該鎖是支持重進入的。那么為了方便大家理解這些知識點,我們先從其類的基本結(jié)構(gòu)講起。具體類結(jié)構(gòu)如下圖所示:

從上圖中我們可以看出,在ReentrantLock類中,定義了三個靜態(tài)內(nèi)部類,Sync、FairSync(公平鎖)、NonfairSync(非公平鎖)。其中Sync繼承了AQS(AbstractQueuedSynchronizer),而FairSync與NonfairSync又分別繼承了Sync。關(guān)于ReentrantLock基本類結(jié)構(gòu)如下所示:
public class ReentrantLock implements Lock, java.io.Serializable {
? ? private final Sync sync;
//默認(rèn)無參構(gòu)造函數(shù),默認(rèn)為非公平鎖
? ? public ReentrantLock() {
? ? ? ? sync = new NonfairSync();
? ? }
//帶參數(shù)的構(gòu)造函數(shù),用戶自己來決定是公平鎖還是非公平鎖
? ? public ReentrantLock(boolean fair) {
? ? ? ? sync = fair ? new FairSync() : new NonfairSync();
? ? }
? ? //抽象基類繼承AQS,公平鎖與非公平鎖繼承該類,并分別實現(xiàn)其lock()方法
? ? abstract static class Sync extends AbstractQueuedSynchronizer {
? ? ? ? abstract void lock();
? ? ? ? //省略部分代碼..
? ? }
//非公平鎖實現(xiàn)
? ? static final class NonfairSync extends Sync {...}
? ? //公平鎖實現(xiàn)
? ? static final class FairSync extends Sync {....}
? ? //鎖實習(xí),根據(jù)具體子類實現(xiàn)調(diào)用
? ? public void lock() {
? ? ? ? sync.lock();
? ? }
//響應(yīng)中斷的獲取鎖
? ? public void lockInterruptibly() throws InterruptedException {
? ? ? ? sync.acquireInterruptibly(1);
? ? }
//嘗試獲取鎖,默認(rèn)采用非公平鎖方法實現(xiàn)
? ? public boolean tryLock() {
? ? ? ? return sync.nonfairTryAcquire(1);
? ? }
//超時獲取鎖
? ? public boolean tryLock(long timeout, TimeUnit unit)
? ? ? ? ? ? throws InterruptedException {
? ? ? ? return sync.tryAcquireNanos(1, unit.toNanos(timeout));
? ? }
//釋放鎖
? ? public void unlock() {
? ? ? ? sync.release(1);
? ? }
? ? //創(chuàng)建鎖條件(從Condetion來理解,就是創(chuàng)建等待隊列)
? ? public Condition newCondition() {
? ? ? ? return sync.newCondition();
? ? }
? ? //省略部分代碼....
}
復(fù)制代碼
這里為了方便大家理解ReentrantLock類的整體結(jié)構(gòu),我省略了一些代碼及重新排列了一些代碼的順序。
從代碼中我們可以看出。整個ReentrantLock類的實現(xiàn)其實都是交給了其內(nèi)部FairSync與NonfairSync兩個類。在ReentrantLock類中有兩個構(gòu)造函數(shù),其中不帶參數(shù)的構(gòu)造函數(shù)中默認(rèn)使用的NonfairSync(非公平鎖)。另一個帶參數(shù)的構(gòu)造函數(shù),用戶自己來決定是FairSync(公平鎖)還是非公平鎖。
重進入實現(xiàn)
在上文中,我們提到了ReentrantLock是支持重進入的,那什么是重進入呢?重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖,而不會被鎖阻塞。那接下來我們看看這個例子,如下所示:
class ReentrantLockDemo {
? ? private static final ReentrantLock lock = new ReentrantLock();
? ? public static void main(String[] args) {
? ? ? ? Thread thread = new Thread(new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? methodA();
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? thread.start();
? ? }
? ? public static void methodA() {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println("我已經(jīng)進入methodA方法了");
? ? ? ? ? ? methodB();//方法A中繼續(xù)調(diào)用方法B
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
? ? public static void methodB() {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println("我已經(jīng)進入methodB方法了");
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
}
//輸出結(jié)果
我已經(jīng)進入methodA方法了
我已經(jīng)進入methodB方法了
復(fù)制代碼
在上述代碼中我們聲明了一個線程調(diào)用methodA()方法。同時在該方法內(nèi)部我們又調(diào)用了methodB()方法。從實際的代碼運行結(jié)果來看,當(dāng)前線程進入方法A之后。在方法B中再次調(diào)用lock.lock();時,該線程并沒有被阻塞。也就是說ReentrantLock是支持重進入的。那下面我們就一起來看看其內(nèi)部的實現(xiàn)原理。
因為ReenTrantLock將具體實現(xiàn)交給了NonfairSync(非公平鎖)與FairSync(公平鎖)。同時又因為上述提到的兩個鎖,關(guān)于重進入的實現(xiàn)又非常相似。所以這里將采用NonfairSync(非公平鎖)的重進入的實現(xiàn),來進行分析。希望讀者朋友們閱讀到這里的時候需要注意,不是我懶哦,是真的很相似哦。
好了下面我們來看代碼。關(guān)于NonfairSync代碼如下所示:
static final class NonfairSync extends Sync {
? ? ? ? final void lock() {
? ? ? ? ? ? if (compareAndSetState(0, 1))////直接獲取同步狀態(tài)成功,那么就不再走嘗試獲取鎖的過程
? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? protected final boolean tryAcquire(int acquires) {
? ? ? ? ? ? return nonfairTryAcquire(acquires);
? ? ? ? }
? ? }
復(fù)制代碼
當(dāng)我們調(diào)用lock()方法時,通過CAS操作將AQS中的state的狀態(tài)設(shè)置為1,如果成功,那么表示獲取同步狀態(tài)成功。那么會接著調(diào)用setExclusiveOwnerThread(Thread thread)方法來設(shè)置當(dāng)前占有鎖的線程。如果失敗,則調(diào)用acquire(int arg)方法來獲取同步狀態(tài)(該方法是屬于AQS中的獨占式獲取同步狀態(tài)的方法,對該方法不熟悉的小伙伴,建議閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer))。而該方法內(nèi)部會調(diào)用tryAcquire(int acquires)來嘗試獲取同步狀態(tài)。通過觀察,我們發(fā)現(xiàn)最終會調(diào)用Sync類中的nonfairTryAcquire(int acquires)方法。我們繼續(xù)跟蹤。
final boolean nonfairTryAcquire(int acquires) {
? ? //獲取當(dāng)前線程
? ? ? ? ? ? final Thread current = Thread.currentThread();
? ? ? ? ? ? int c = getState();
? ? ? ? ? ? //(1)判斷同步狀態(tài),如果未設(shè)置,則設(shè)置同步狀態(tài)
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {
? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? //(2)如果當(dāng)前線程已經(jīng)獲取了同步狀態(tài),則增加同步狀態(tài)的值。
? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {
? ? ? ? ? ? ? ? int nextc = c + acquires;
? ? ? ? ? ? ? ? if (nextc < 0) // overflow
? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");
? ? ? ? ? ? ? ? setState(nextc);
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? return false;
? ? ? ? }
復(fù)制代碼
從代碼上來看,該方法主要走兩個步驟,具體如下所示:
(1)先判斷同步狀態(tài), 如果未曾設(shè)置,則設(shè)置同步狀態(tài),并設(shè)置當(dāng)前占有鎖的線程。
(2)判斷是否是同一線程,如果當(dāng)前線程已經(jīng)獲取了同步狀態(tài)(也就是獲取了鎖),那么增加同步狀態(tài)的值。
也就是說,如果同一個鎖獲取了鎖N(N為正整數(shù))次,那么對應(yīng)的同步狀態(tài)(state)也就等于N。那么接下來的問題來了,如果當(dāng)前線程重復(fù)N次獲取了鎖,那么該線程是否需要釋放鎖N次呢?答案當(dāng)然是必須的。當(dāng)我們調(diào)用ReenTrantLock的unlock()方法來釋放同步狀態(tài)(也就是釋放鎖)時,內(nèi)部會調(diào)用sync.release(1);。最終會調(diào)用Sync類的tryRelease(int releases)方法。具體代碼如下所示:
protected final boolean tryRelease(int releases) {
? ? ? ? ? ? int c = getState() - releases;
? ? ? ? ? ? if (Thread.currentThread() != getExclusiveOwnerThread())
? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();
? ? ? ? ? ? boolean free = false;
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? free = true;
? ? ? ? ? ? ? ? setExclusiveOwnerThread(null);
? ? ? ? ? ? }
? ? ? ? ? ? setState(c);
? ? ? ? ? ? return free;
? ? ? ? }
復(fù)制代碼
從代碼中,我們可以知道,每調(diào)用一次unlock()方法會將當(dāng)前同步狀態(tài)減一。也就是說如果當(dāng)前線程獲取了鎖N次,那么獲取鎖的相應(yīng)線程也需要調(diào)用unlock()方法N次。這也是為什么我們在之前的重入鎖例子中,為什么methodB方法中也要釋放鎖的原因。
非公平鎖
在ReentrantLock中有著非公平鎖與公平鎖的概念,這里我先簡單的介紹一下公平這兩個字的含義。?這里的公平是指線程獲取鎖的順序。也就是說鎖的獲取順序是按照當(dāng)前線程請求的絕對時間順序,當(dāng)然前提條件下是該線程獲取鎖成功?。
那么接下來,我們來分析在ReentrantLock中的非公平鎖的具體實現(xiàn)。
這里需要大家具備AQS(AbstractQueuedSynchronizer)類的相關(guān)知識。如果大家不熟悉這塊的知識。建議大家閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer)。
static final class NonfairSync extends Sync {
? ? ? ? private static final long serialVersionUID = 7316153563782823691L;
? ? ? ? final void lock() {
? ? ? ? ? ? if (compareAndSetState(0, 1))//直接獲取同步狀態(tài)成功,那么就不再走嘗試獲取鎖的過程
? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? //省略部分代碼...
? ? }
復(fù)制代碼
當(dāng)在ReentrantLock在非公平鎖的模式下,去調(diào)用lock()方法。那么接下來最終會走AQS(AbstractQueuedSynchronizer)下的acquire(int arg)(獨占式的獲取同步狀態(tài)),也就是如下代碼:
public final void acquire(int arg) {
? ? ? ? if (!tryAcquire(arg) &&
? ? ? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
? ? ? ? ? ? selfInterrupt();
? ? }
復(fù)制代碼
那么結(jié)合之前我們所講的AQS知識,在多個線程在獨占式請求共享狀態(tài)下(也就是請求鎖)的情況下,在AQS中的同步隊列中的線程節(jié)點情況如下圖所示:

那么我們試想一種情況,當(dāng)Nod1中的線程執(zhí)行完相應(yīng)任務(wù)后,釋放鎖后。這個時候本來該喚醒當(dāng)前線程節(jié)點的下一個節(jié)點,也就是Node2中的線程。這個時候突然另一線程突然來獲取線程(這里我們用節(jié)點Node5來表示)。具體情況如下圖所示:

那么根據(jù)AQS中獨占式獲取同步狀態(tài)的邏輯。只要Node5對應(yīng)的線程獲取同步狀態(tài)成功。那么就會出現(xiàn)下面的這種情況,具體情況如下圖所示:在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流裙。交流學(xué)習(xí)裙號:821169538,里面會分享一些資深架構(gòu)師錄制的視頻錄像

從上圖中我們可以看出,由于Node5對象的線程搶占了獲取同步狀態(tài)(獲取鎖)的機會,本身應(yīng)該被喚醒的Node2線程節(jié)點。因為獲取同步狀態(tài)失敗。所以只有再次的陷入阻塞。那么綜上。我們可以知道。非公平鎖獲取同步狀態(tài)(獲取鎖)時不會考慮同步隊列中中等待的問題。會直接嘗試獲取鎖。也就是會存在后申請,但是會先獲得同步狀態(tài)(獲取鎖)的情況。
公平鎖
理解了非公平鎖,再來理解公平鎖就非常簡單了。下面我們來看一下公平鎖與非公平鎖的加鎖的源碼:

從源碼我們可以看出,非公平鎖與公平鎖之間的代碼唯一區(qū)別就是多了一個判斷條件!hasQueuedPredecessors()(圖中紅框所示)。那我們查看其源碼(該代碼在AQS中,強烈建議閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer)
)
public final boolean hasQueuedPredecessors() {
? ? ? ? Node t = tail;
? ? ? ? Node h = head;
? ? ? ? Node s;
? ? ? ? return h != t &&
? ? ? ? ? ? ((s = h.next) == null || s.thread != Thread.currentThread());
? ? }
復(fù)制代碼
代碼理解理解起來非常簡單,就是判斷當(dāng)前當(dāng)前head節(jié)點的next節(jié)點是不是當(dāng)前請求同步狀態(tài)(請求鎖)的線程。也就是語句((s = h.next) == null || s.thread != Thread.currentThread()。那么接下來結(jié)合AQS中的同步隊列我們可以得到下圖:

那么綜上我們可以得出,公平鎖保證了線程請求的同步狀態(tài)(請求鎖)的順序。不會出現(xiàn)另一個線程搶占的情況。