最近在忙公司的項(xiàng)目疲恢,現(xiàn)在終于有時(shí)間來(lái)寫博客啦~開心開心
前言
通過(guò)前面的文章,我們已經(jīng)了解了AQS(AbstractQueuedSynchronizer)
內(nèi)部的實(shí)現(xiàn)與基本原理。現(xiàn)在我們來(lái)了解一下,Java中為我們提供的Lock機(jī)制下的鎖實(shí)現(xiàn)--ReentrantLock(重入鎖)
,閱讀該篇文章之前妻顶,希望你已閱讀以下文章。
- Java并發(fā)編程之鎖機(jī)制之Lock接口
- Java并發(fā)編程之鎖機(jī)制之AQS(AbstractQueuedSynchronizer)
- Java并發(fā)編程之鎖機(jī)制之LockSupport工具
- Java并發(fā)編程之鎖機(jī)制之Condition接口
ReentrantLock基本介紹
ReentrantLock
是一種可重入
的互斥鎖
蜒车,它具有與使用synchronized
方法和語(yǔ)句所訪問(wèn)的隱式監(jiān)視器鎖相同的一些基本行為和語(yǔ)義讳嘱,但功能更強(qiáng)大。
ReentrantLock
將由最近成功獲得鎖酿愧,并且還沒(méi)有釋放該鎖的線程所擁有沥潭。當(dāng)鎖沒(méi)有被另一個(gè)線程所擁有時(shí),調(diào)用 lock 的線程將成功獲取該鎖并返回嬉挡。如果當(dāng)前線程已經(jīng)擁有該鎖钝鸽,此方法將立即返回∨痈郑可以使用isHeldByCurrentThread()
和 getHoldCount()
方法來(lái)檢查此情況是否發(fā)生拔恰。
此類的構(gòu)造方法接受一個(gè)可選的公平
參數(shù)。當(dāng)設(shè)置為 true 時(shí)(也是當(dāng)前ReentrantLock為公平鎖的情況
)基括,在多個(gè)線程的爭(zhēng)用下颜懊,這些鎖傾向于將訪問(wèn)權(quán)授予等待時(shí)間最長(zhǎng)的線程。否則此鎖將無(wú)法保證任何特定訪問(wèn)順序风皿。與采用默認(rèn)設(shè)置(使用不公平鎖)相比河爹,使用公平鎖的程序在許多線程訪問(wèn)時(shí)表現(xiàn)為很低的總體吞吐量(即速度很慢,常常極其慢)桐款,但是在獲得鎖和保證鎖分配的均衡性時(shí)差異較小咸这。不過(guò)要注意的是,公平鎖不能保證線程調(diào)度的公平性魔眨。因此媳维,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機(jī)會(huì)酿雪,這種情況發(fā)生在其他活動(dòng)線程沒(méi)有被處理并且目前并未持有鎖時(shí)。還要注意的是侄刽,未定時(shí)的 tryLock 方法并沒(méi)有使用公平設(shè)置执虹。因?yàn)榧词蛊渌€程正在等待,只要該鎖是可用的唠梨,此方法就可以獲得成功。
ReentrantLock 類基本結(jié)構(gòu)
通過(guò)上文的簡(jiǎn)單介紹后侥啤,我相信很多小伙伴還是一臉懵逼当叭,只知道上文我們提到了ReentrantLock
與synchronized
相比有相同的語(yǔ)義,同時(shí)其內(nèi)部分為了公平鎖
與非公平鎖
兩種鎖的類型盖灸,且該鎖是支持重進(jìn)入
的蚁鳖。那么為了方便大家理解這些知識(shí)點(diǎn),我們先從其類的基本結(jié)構(gòu)講起赁炎。具體類結(jié)構(gòu)如下圖所示:
從上圖中我們可以看出醉箕,在ReentrantLock
類中,定義了三個(gè)靜態(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)無(wú)參構(gòu)造函數(shù)吴旋,默認(rèn)為非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//帶參數(shù)的構(gòu)造函數(shù)损肛,用戶自己來(lái)決定是公平鎖還是非公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//抽象基類繼承AQS,公平鎖與非公平鎖繼承該類荣瑟,并分別實(shí)現(xiàn)其lock()方法
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
//省略部分代碼..
}
//非公平鎖實(shí)現(xiàn)
static final class NonfairSync extends Sync {...}
//公平鎖實(shí)現(xiàn)
static final class FairSync extends Sync {....}
//鎖實(shí)現(xiàn)治拿,根據(jù)具體子類實(shí)現(xiàn)調(diào)用
public void lock() {
sync.lock();
}
//響應(yīng)中斷的獲取鎖
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//嘗試獲取鎖,默認(rèn)采用非公平鎖方法實(shí)現(xiàn)
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//超時(shí)獲取鎖
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來(lái)理解笆焰,就是創(chuàng)建等待隊(duì)列)
public Condition newCondition() {
return sync.newCondition();
}
//省略部分代碼....
}
這里為了方便大家理解
ReentrantLock
類的整體結(jié)構(gòu)劫谅,我省略了一些代碼及重新排列了一些代碼的順序。
從代碼中我們可以看出仙辟。整個(gè)ReentrantLock
類的實(shí)現(xiàn)其實(shí)都是交給了其內(nèi)部FairSync
與NonfairSync
兩個(gè)類同波。在ReentrantLock
類中有兩個(gè)構(gòu)造函數(shù)未檩,其中不帶參數(shù)的構(gòu)造函數(shù)中默認(rèn)使用的NonfairSync(非公平鎖)
。另一個(gè)帶參數(shù)的構(gòu)造函數(shù),用戶自己來(lái)決定是FairSync(公平鎖)
還是非公平鎖晴楔。
重進(jìn)入實(shí)現(xiàn)
在上文中,我們提到了ReentrantLock
是支持重進(jìn)入的幔翰,那什么是重進(jìn)入呢贡定?重進(jìn)入是指任意線程在獲取到鎖之后能夠再次獲取該鎖旋炒,而不會(huì)被鎖阻塞
铣除。那接下來(lái)我們看看這個(gè)例子敲长,如下所示:
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)進(jìn)入methodA方法了");
methodB();//方法A中繼續(xù)調(diào)用方法B
} finally {
lock.unlock();
}
}
public static void methodB() {
lock.lock();
try {
System.out.println("我已經(jīng)進(jìn)入methodB方法了");
} finally {
lock.unlock();
}
}
}
//輸出結(jié)果
我已經(jīng)進(jìn)入methodA方法了
我已經(jīng)進(jìn)入methodB方法了
在上述代碼中我們聲明了一個(gè)線程調(diào)用methodA()方法杠茬。同時(shí)在該方法內(nèi)部我們又調(diào)用了methodB()方法。從實(shí)際的代碼運(yùn)行結(jié)果來(lái)看礁击,當(dāng)前線程進(jìn)入方法A之后盐杂。在方法B中再次調(diào)用lock.lock();
時(shí),該線程并沒(méi)有被阻塞哆窿。也就是說(shuō)ReentrantLock
是支持重進(jìn)入的链烈。那下面我們就一起來(lái)看看其內(nèi)部的實(shí)現(xiàn)原理。
因?yàn)?code>ReenTrantLock將具體實(shí)現(xiàn)交給了NonfairSync(非公平鎖)
與FairSync(公平鎖)
挚躯。同時(shí)又因?yàn)樯鲜鎏岬降膬蓚€(gè)鎖强衡,關(guān)于重進(jìn)入的實(shí)現(xiàn)又非常相似。所以這里將采用NonfairSync(非公平鎖)
的重進(jìn)入的實(shí)現(xiàn)码荔,來(lái)進(jìn)行分析漩勤。希望讀者朋友們閱讀到這里的時(shí)候需要注意,不是我懶哦缩搅,是真的很相似哦越败。
好了下面我們來(lái)看代碼。關(guān)于NonfairSync代碼如下所示:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))////直接獲取同步狀態(tài)成功硼瓣,那么就不再走嘗試獲取鎖的過(guò)程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
當(dāng)我們調(diào)用lock()方法時(shí)究飞,通過(guò)CAS操作將AQS中的state的狀態(tài)設(shè)置為1,如果成功堂鲤,那么表示獲取同步狀態(tài)成功亿傅。那么會(huì)接著調(diào)用setExclusiveOwnerThread(Thread thread)
方法來(lái)設(shè)置當(dāng)前占有鎖的線程。如果失敗瘟栖,則調(diào)用acquire(int arg)
方法來(lái)獲取同步狀態(tài)(該方法是屬于AQS中的獨(dú)占式獲取同步狀態(tài)的方法葵擎,對(duì)該方法不熟悉的小伙伴,建議閱讀 Java并發(fā)編程之鎖機(jī)制之AQS(AbstractQueuedSynchronizer))慢宗。而該方法內(nèi)部會(huì)調(diào)用tryAcquire(int acquires)
來(lái)嘗試獲取同步狀態(tài)坪蚁。通過(guò)觀察奔穿,我們發(fā)現(xiàn)最終會(huì)調(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;
}
從代碼上來(lái)看,該方法主要走兩個(gè)步驟译打,具體如下所示:
- (1)先判斷同步狀態(tài)耗拓, 如果未曾設(shè)置,則設(shè)置同步狀態(tài)奏司,并設(shè)置當(dāng)前占有鎖的線程乔询。
- (2)判斷是否是同一線程,如果當(dāng)前線程已經(jīng)獲取了同步狀態(tài)(也就是獲取了鎖)韵洋,那么增加同步狀態(tài)的值竿刁。
也就是說(shuō),如果同一個(gè)鎖獲取了鎖N(N為正整數(shù)
)次搪缨,那么對(duì)應(yīng)的同步狀態(tài)(state)
也就等于N食拜。那么接下來(lái)的問(wèn)題來(lái)了,如果當(dāng)前線程重復(fù)N次獲取了鎖副编,那么該線程是否需要釋放鎖N次呢负甸?
答案當(dāng)然是必須的。當(dāng)我們調(diào)用ReenTrantLock
的unlock()方法來(lái)釋放同步狀態(tài)(也就是釋放鎖)時(shí)痹届,內(nèi)部會(huì)調(diào)用sync.release(1);
呻待。最終會(huì)調(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;
}
從代碼中队腐,我們可以知道带污,每調(diào)用一次unlock()
方法會(huì)將當(dāng)前同步狀態(tài)減一。也就是說(shuō)如果當(dāng)前線程獲取了鎖N次香到,那么獲取鎖的相應(yīng)線程也需要調(diào)用unlock()
方法N次鱼冀。這也是為什么我們?cè)谥暗闹厝腈i例子中,為什么methodB
方法中也要釋放鎖的原因悠就。
非公平鎖
在ReentrantLock中有著非公平鎖
與公平鎖
的概念千绪,這里我先簡(jiǎn)單的介紹一下公平
這兩個(gè)字的含義。這里的公平是指線程獲取鎖的順序梗脾。也就是說(shuō)鎖的獲取順序是按照當(dāng)前線程請(qǐng)求的絕對(duì)時(shí)間順序荸型,當(dāng)然前提條件下是該線程獲取鎖成功。
那么接下來(lái)炸茧,我們來(lái)分析在ReentrantLock中的非公平鎖的具體實(shí)現(xiàn)瑞妇。
這里需要大家具備
AQS(AbstractQueuedSynchronizer)
類的相關(guān)知識(shí)稿静。如果大家不熟悉這塊的知識(shí)。建議大家閱讀 Java并發(fā)編程之鎖機(jī)制之AQS(AbstractQueuedSynchronizer)辕狰。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))//直接獲取同步狀態(tài)成功改备,那么就不再走嘗試獲取鎖的過(guò)程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//省略部分代碼...
}
當(dāng)在ReentrantLock在非公平鎖的模式下
,去調(diào)用lock()方法蔓倍。那么接下來(lái)最終會(huì)走AQS(AbstractQueuedSynchronizer)
下的acquire(int arg)(獨(dú)占式的獲取同步狀態(tài))
悬钳,也就是如下代碼:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
那么結(jié)合之前我們所講的AQS知識(shí),在多個(gè)線程在獨(dú)占式
請(qǐng)求共享狀態(tài)下(也就是請(qǐng)求鎖)的情況下偶翅,在AQS中的同步隊(duì)列中的線程節(jié)點(diǎn)情況如下圖所示:
那么我們?cè)囅胍环N情況默勾,當(dāng)Nod1中的線程執(zhí)行完相應(yīng)任務(wù)后,釋放鎖后聚谁。這個(gè)時(shí)候本來(lái)該喚醒當(dāng)前線程節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
,也就是Node2中的線程
母剥。這個(gè)時(shí)候突然另一線程突然來(lái)獲取線程(這里我們用節(jié)點(diǎn)Node5
來(lái)表示)。具體情況如下圖所示:
那么根據(jù)AQS中獨(dú)占式獲取同步狀態(tài)的邏輯形导。只要Node5對(duì)應(yīng)的線程獲取同步狀態(tài)成功
媳搪。那么就會(huì)出現(xiàn)下面的這種情況,具體情況如下圖所示:
從上圖中我們可以看出骤宣,由于Node5對(duì)象的線程搶占了獲取同步狀態(tài)(獲取鎖)的機(jī)會(huì),本身應(yīng)該被喚醒的Node2
線程節(jié)點(diǎn)序愚。因?yàn)楂@取同步狀態(tài)失敗憔披。所以只有再次的陷入阻塞。那么綜上爸吮。我們可以知道芬膝。非公平鎖獲取同步狀態(tài)(獲取鎖)時(shí)不會(huì)考慮同步隊(duì)列中中等待的問(wèn)題。會(huì)直接嘗試獲取鎖形娇。也就是會(huì)存在后申請(qǐng)锰霜,但是會(huì)先獲得同步狀態(tài)(獲取鎖)的情況。
公平鎖
理解了非公平鎖桐早,再來(lái)理解公平鎖就非常簡(jiǎn)單了癣缅。下面我們來(lái)看一下公平鎖與非公平鎖的加鎖的源碼:
從源碼我們可以看出,非公平鎖與公平鎖之間的代碼唯一區(qū)別就是多了一個(gè)判斷條件
!hasQueuedPredecessors()(圖中紅框所示)
哄酝。那我們查看其源碼(該代碼在AQS中友存,強(qiáng)烈建議閱讀 Java并發(fā)編程之鎖機(jī)制之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());
}
代碼理解理解起來(lái)非常簡(jiǎn)單,就是判斷當(dāng)前當(dāng)前head節(jié)點(diǎn)的next節(jié)點(diǎn)是不是當(dāng)前請(qǐng)求同步狀態(tài)(請(qǐng)求鎖)的線程陶衅。也就是語(yǔ)句
((s = h.next) == null || s.thread != Thread.currentThread()
屡立。那么接下來(lái)結(jié)合AQS中的同步隊(duì)列我們可以得到下圖:
那么綜上我們可以得出,公平鎖保證了線程請(qǐng)求的同步狀態(tài)(請(qǐng)求鎖)的順序搀军。不會(huì)出現(xiàn)另一個(gè)線程搶占的情況膨俐。
最后
該文章參考以下圖書勇皇,站在巨人的肩膀上》俅蹋可以看得更遠(yuǎn)敛摘。
- 《Java并發(fā)編程的藝術(shù)》