Java并發(fā)系列之AbstractQueuedSynchronizer源碼分析(概要分析)
學(xué)習(xí)Java并發(fā)編程不得不去了解一下java.util.concurrent這個(gè)包,這個(gè)包下面有許多我們經(jīng)常用到的并發(fā)工具類,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等。而這些類的底層實(shí)現(xiàn)都依賴于AbstractQueuedSynchronizer這個(gè)類,由此可見(jiàn)這個(gè)類的重要性。所以在Java并發(fā)系列文章中我首先對(duì)AbstractQueuedSynchronizer這個(gè)類進(jìn)行分析,由于這個(gè)類比較重要,而且代碼比較長(zhǎng),為了盡可能分析的透徹一些,我決定用四篇文章對(duì)該類進(jìn)行一個(gè)比較完整的介紹。本篇文章作為概要介紹主要是讓讀者們對(duì)該類有個(gè)初步了解。為了敘述簡(jiǎn)單,后續(xù)有些地方會(huì)用AQS代表這個(gè)類。
1. AbstractQueuedSynchronizer這個(gè)類是干嘛的?
相信要許多讀者使用過(guò)ReentrantLock,但是卻不知道AbstractQueuedSynchronizer的存在。其實(shí)ReentrantLock實(shí)現(xiàn)了一個(gè)內(nèi)部類Sync,該內(nèi)部類繼承了AbstractQueuedSynchronizer,所有鎖機(jī)制的實(shí)現(xiàn)都是依賴于Sync內(nèi)部類,也可以說(shuō)ReentrantLock的實(shí)現(xiàn)就是依賴于AbstractQueuedSynchronizer類。于此類似,CountDownLatch, CyclicBarrier, Semaphore這些類也是采用同樣的方式來(lái)實(shí)現(xiàn)自己對(duì)于鎖的控制??梢?jiàn),AbstractQueuedSynchronizer是這些類的基石。那么AQS內(nèi)部到底實(shí)現(xiàn)了什么以至于所以這些類都要依賴于它呢?可以這樣說(shuō),AQS為這些類提供了基礎(chǔ)設(shè)施,也就是提供了一個(gè)密碼鎖,這些類擁有了密碼鎖之后可以自己來(lái)設(shè)置密碼鎖的密碼。此外,AQS還提供了一個(gè)排隊(duì)區(qū),并且提供了一個(gè)線程訓(xùn)導(dǎo)員,我們知道線程就像一個(gè)原始的野蠻人,它不懂得講禮貌,它只會(huì)橫沖直撞,所以你得一步一步去教它,告訴它什么時(shí)候需要去排隊(duì)了,要到哪里去排隊(duì),排隊(duì)前要做些什么,排隊(duì)后要做些什么。這些教化工作全部都由AQS幫你完成了,從它這里教化出來(lái)的線程都變的非常文明懂禮貌,不再是原始的野蠻人,所以以后我們只需要和這些文明的線程打交道就行了,千萬(wàn)不要和原始線程有過(guò)多的接觸!
2. 為何說(shuō)AbstractQueuedSynchronizer提供了一把密碼鎖?
//同步隊(duì)列的頭結(jié)點(diǎn)
private transient volatile Node head;
//同步隊(duì)列的尾結(jié)點(diǎn)
private transient volatile Node tail;
//同步狀態(tài)
private volatile int state;
//獲取同步狀態(tài)
protected final int getState() {
return state;
}
//設(shè)置同步狀態(tài)
protected final void setState(int newState) {
state = newState;
}
//以CAS方式設(shè)置同步狀態(tài)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
上面的代碼列出了AQS的所有成員變量,可以看到AQS的成員變量只有三個(gè),分別是同步隊(duì)列頭結(jié)點(diǎn)引用,同步隊(duì)列尾結(jié)點(diǎn)引用以及同步狀態(tài)。注意,這三個(gè)成員變量都使用了volatile關(guān)鍵字進(jìn)行修飾,這就確保了多個(gè)線程對(duì)它的修改都是內(nèi)存可見(jiàn)的。整個(gè)類的核心就是這個(gè)同步狀態(tài),可以看到同步狀態(tài)其實(shí)就是一個(gè)int型的變量,大家可以把這個(gè)同步狀態(tài)看成一個(gè)密碼鎖,而且還是從房間里面鎖起來(lái)的密碼鎖,state具體的值就相當(dāng)于密碼控制著密碼鎖的開(kāi)合。當(dāng)然這個(gè)鎖的密碼是多少就由各個(gè)子類來(lái)規(guī)定了,例如在ReentrantLock中,state等于0表示鎖是開(kāi)的,state大于0表示鎖是鎖著的,而在Semaphore中,state大于0表示鎖是開(kāi)的,state等于0表示鎖是鎖著的。
3. AbstractQueuedSynchronizer的排隊(duì)區(qū)是怎樣實(shí)現(xiàn)的?

AbstractQueuedSynchronizer內(nèi)部其實(shí)有兩個(gè)排隊(duì)區(qū),一個(gè)是同步隊(duì)列,一個(gè)是條件隊(duì)列。從上圖可以看出,同步隊(duì)列只有一條,而條件隊(duì)列可以有多條。同步隊(duì)列的結(jié)點(diǎn)分別持有前后結(jié)點(diǎn)的引用,而條件隊(duì)列的結(jié)點(diǎn)只有一個(gè)指向后繼結(jié)點(diǎn)的引用。圖中T表示線程,每個(gè)結(jié)點(diǎn)包含一個(gè)線程,線程在獲取鎖失敗后首先進(jìn)入同步隊(duì)列排隊(duì),而想要進(jìn)入條件隊(duì)列該線程必須持有鎖才行。接下來(lái)我們看看隊(duì)列中每個(gè)結(jié)點(diǎn)的結(jié)構(gòu)。
//同步隊(duì)列的結(jié)點(diǎn)
static final class Node {
static final Node SHARED = new Node(); //表示當(dāng)前線程以共享模式持有鎖
static final Node EXCLUSIVE = null; //表示當(dāng)前線程以獨(dú)占模式持有鎖
static final int CANCELLED = 1; //表示當(dāng)前結(jié)點(diǎn)已經(jīng)取消獲取鎖
static final int SIGNAL = -1; //表示后繼結(jié)點(diǎn)的線程需要運(yùn)行
static final int CONDITION = -2; //表示當(dāng)前結(jié)點(diǎn)在條件隊(duì)列中排隊(duì)
static final int PROPAGATE = -3; //表示后繼結(jié)點(diǎn)可以直接獲取鎖
volatile int waitStatus; //表示當(dāng)前結(jié)點(diǎn)的等待狀態(tài)
volatile Node prev; //表示同步隊(duì)列中的前繼結(jié)點(diǎn)
volatile Node next; //表示同步隊(duì)列中的后繼結(jié)點(diǎn)
volatile Thread thread; //當(dāng)前結(jié)點(diǎn)持有的線程引用
Node nextWaiter; //表示條件隊(duì)列中的后繼結(jié)點(diǎn)
//當(dāng)前結(jié)點(diǎn)狀態(tài)是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回當(dāng)前結(jié)點(diǎn)的前繼結(jié)點(diǎn)
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null) {
throw new NullPointerException();
} else {
return p;
}
}
//構(gòu)造器1
Node() {}
//構(gòu)造器2, 默認(rèn)用這個(gè)構(gòu)造器
Node(Thread thread, Node mode) {
//注意持有模式是賦值給nextWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//構(gòu)造器3, 只在條件隊(duì)列中用到
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Node代表同步隊(duì)列和條件隊(duì)列中的一個(gè)結(jié)點(diǎn),它是AbstractQueuedSynchronizer的內(nèi)部類。Node有很多屬性,比如持有模式,等待狀態(tài),同步隊(duì)列中的前繼和后繼,以及條件隊(duì)列中的后繼引用等等??梢园淹疥?duì)列和條件隊(duì)列看成是排隊(duì)區(qū),每個(gè)結(jié)點(diǎn)看成是排隊(duì)區(qū)的座位,將線程看成是排隊(duì)的客人??腿藙倎?lái)時(shí)會(huì)先去敲敲門,看看鎖有沒(méi)有開(kāi),如果鎖沒(méi)開(kāi)它就會(huì)去排隊(duì)區(qū)領(lǐng)取一個(gè)號(hào)碼牌,聲明自己想要以什么樣的方式來(lái)持有鎖,最后再到隊(duì)列的末尾進(jìn)行排隊(duì)。
4 怎樣理解獨(dú)占模式和共享模式?
前面講到每個(gè)客人在排隊(duì)前會(huì)領(lǐng)取一個(gè)號(hào)碼牌,聲明自己想要以什么樣的方式來(lái)占有鎖,占有鎖的方式分為獨(dú)占模式和共享模式,那么怎樣來(lái)理解獨(dú)占模式和共享模式呢?實(shí)在找不到什么好的比喻,大家可以聯(lián)想一下公共廁所,獨(dú)占模式的人比較霸道,老子要么就不進(jìn),進(jìn)來(lái)了就不許別人再進(jìn)了,自己一個(gè)人獨(dú)自占用整個(gè)廁所。共享模式的人就沒(méi)那么講究了,當(dāng)它發(fā)現(xiàn)這個(gè)廁所已經(jīng)可以用了之后,它自己進(jìn)來(lái)還不算,還得熱心的問(wèn)下后面的人介不介意一起用,如果后面的人不介意一起使用那就不用再排隊(duì)了大家一起上就是了, 當(dāng)然如果后面的人介意那就只好留在隊(duì)列里繼續(xù)排隊(duì)了。
5 怎樣理解結(jié)點(diǎn)的等待狀態(tài)?
我們還看到每個(gè)結(jié)點(diǎn)都有一個(gè)等待狀態(tài),這個(gè)等待狀態(tài)分為CANCELLED,SIGNAL,CONDITION,PROPAGATE四種狀態(tài)??梢詫⑦@個(gè)等待狀態(tài)看作是掛在座位旁邊的牌子,標(biāo)識(shí)當(dāng)前座位上的人的等待狀態(tài)。這個(gè)牌子的狀態(tài)不僅自己可以修改,其他人也可以修改。例如當(dāng)這個(gè)線程在排隊(duì)過(guò)程中已經(jīng)打算放棄了,它就會(huì)將自己座位上的牌子設(shè)置為CANCELLED,這樣其他人看到了就可以將它清理出隊(duì)列。還有一種情況是,當(dāng)線程在座位上要睡著之前,它怕自己睡過(guò)了頭,就會(huì)將前面位置上的牌子改為SIGNAL,因?yàn)槊總€(gè)人在離開(kāi)隊(duì)列前都會(huì)回到自己座位上看一眼,如果看到牌子上狀態(tài)為SIGNAL,它就會(huì)去喚醒下一個(gè)人。只有保證前面位置上的牌子為SIGNAL,當(dāng)前線程才會(huì)安心的睡去。CONDITION狀態(tài)表示該線程在條件隊(duì)列中排隊(duì),PROPAGATE狀態(tài)提醒后面來(lái)的線程可以直接獲取鎖,這個(gè)狀態(tài)只在共享模式用到,后面單獨(dú)講共享模式的時(shí)候會(huì)講到。
6. 結(jié)點(diǎn)進(jìn)入同步隊(duì)列時(shí)會(huì)進(jìn)行哪些操作?
//結(jié)點(diǎn)入隊(duì)操作, 返回前一個(gè)結(jié)點(diǎn)
private Node enq(final Node node) {
for (;;) {
//獲取同步隊(duì)列尾結(jié)點(diǎn)引用
Node t = tail;
//如果尾結(jié)點(diǎn)為空說(shuō)明同步隊(duì)列還沒(méi)有初始化
if (t == null) {
//初始化同步隊(duì)列
if (compareAndSetHead(new Node())) {
tail = head;
}
} else {
//1.指向當(dāng)前尾結(jié)點(diǎn)
node.prev = t;
//2.設(shè)置當(dāng)前結(jié)點(diǎn)為尾結(jié)點(diǎn)
if (compareAndSetTail(t, node)) {
//3.將舊的尾結(jié)點(diǎn)的后繼指向新的尾結(jié)點(diǎn)
t.next = node;
//for循環(huán)唯一的出口
return t;
}
}
}
}
注意,入隊(duì)操作使用一個(gè)死循環(huán),只有成功將結(jié)點(diǎn)添加到同步隊(duì)列尾部才會(huì)返回,返回結(jié)果是同步隊(duì)列原先的尾結(jié)點(diǎn)。下圖演示了整個(gè)操作過(guò)程。

讀者需要注意添加尾結(jié)點(diǎn)的順序,分為三步:指向尾結(jié)點(diǎn),CAS更改尾結(jié)點(diǎn),將舊尾結(jié)點(diǎn)的后繼指向當(dāng)前結(jié)點(diǎn)。在并發(fā)環(huán)境中這三步操作不一定能保證完成,所以在清空同步隊(duì)列所有已取消的結(jié)點(diǎn)這一操作中,為了尋找非取消狀態(tài)的結(jié)點(diǎn),不是從前向后遍歷而是從后向前遍歷的。還有就是每個(gè)結(jié)點(diǎn)進(jìn)入隊(duì)列中時(shí)它的等待狀態(tài)是為0,只有后繼結(jié)點(diǎn)的線程需要掛起時(shí)才會(huì)將前面結(jié)點(diǎn)的等待狀態(tài)改為SIGNAL。
注:以上全部分析基于JDK1.7,不同版本間會(huì)有差異,讀者需要注意。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決scala.collection.mutable.Map寫(xiě)入的問(wèn)題
這篇文章主要介紹了解決scala.collection.mutable.Map寫(xiě)入的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
jackson json序列化實(shí)現(xiàn)首字母大寫(xiě),第二個(gè)字母需小寫(xiě)
這篇文章主要介紹了jackson json序列化實(shí)現(xiàn)首字母大寫(xiě),第二個(gè)字母需小寫(xiě)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
SpringBoot使用Hibernate攔截器實(shí)現(xiàn)時(shí)間自動(dòng)注入的操作代碼
這篇文章主要介紹了SpringBoot使用Hibernate攔截器實(shí)現(xiàn)時(shí)間自動(dòng)注入的操作代碼,主要包括hibernate攔截器的相關(guān)知識(shí),結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10

