前言
在前面的文章中剂陡,我曾提到過靡砌,整個Lock接口下實(shí)現(xiàn)的鎖機(jī)制中AQS(AbstractQueuedSynchronizer颤介,下文都稱之為AQS)
與Condition
才是真正的實(shí)現(xiàn)者。也就說Condition
在整個同步組件的基礎(chǔ)框架中也起著非常重要的作用菜秦,既然它如此重要與犀利,那么現(xiàn)在我們就一起去了解其內(nèi)部的實(shí)際原理與具體邏輯舶掖。
在閱讀該文章之前球昨,我由衷的建議先閱讀《Java并發(fā)編程之鎖機(jī)制之AQS》與《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》這兩篇文章。因?yàn)檎麄€Condtion的內(nèi)部機(jī)制與邏輯都離不開以上兩篇文章提到的知識點(diǎn)眨攘。
Condition接口方法介紹
在正式介紹Condtion之前主慰,我們可以先了解其中聲明的方法嚣州。具體方法聲明,如下表所示:
從該表中共螺,我們可以看出其內(nèi)部定義了等待(以await開頭系列方法)
與通知(以singal開頭的系列方法)
兩種類型的方法该肴,類似于Object對象的wait()
與notify()/NotifyAll()
方法來對線程的阻塞與喚醒。
ConditionObject介紹
在實(shí)際使用中藐不,Condition接口實(shí)現(xiàn)類是AQS
中的內(nèi)部類ConditionObject
匀哄。在其內(nèi)部維護(hù)了一個FIFO(first in first out)
的隊列(這里我們稱之為等待隊列
,你也可以叫做阻塞隊列佳吞,看每個人的理解)拱雏,通過與AQS中的同步隊列
配合使用,來控制獲取共享資源的線程底扳。
等待隊列
等待隊列是ConditionObjec
中內(nèi)部的一個FIFO(first in first out)
的隊列铸抑,在隊列中的每個節(jié)點(diǎn)都包含了一個線程引用,且該線程就是在ConditionObject對象上阻塞的線程衷模。需要注意的是鹊汛,在等待隊列中的節(jié)點(diǎn)是復(fù)用了AQS
中Node類
的定義。換句話說阱冶,在AQS
中維護(hù)的同步隊列與ConditionObjec
中維護(hù)的等待隊列中的節(jié)點(diǎn)類型都是AQS.Node
類型刁憋。(關(guān)于AbstractQueuedSynchronizer.Node
類的介紹,大家可以參看《Java并發(fā)編程之鎖機(jī)制之AQS》文章中的描述)木蹬。
在ConditionObject類中也分別定義了firstWaiter
與lastWaiter
兩個指針至耻,分別指向等待隊列中頭部與尾部。當(dāng)實(shí)際線程調(diào)用其以await開頭
的系列方法后镊叁。會將該線程構(gòu)造為Node節(jié)點(diǎn)尘颓。添加等待隊列中的尾部。關(guān)于等待隊列的基本結(jié)構(gòu)如下圖所示:
對于等待隊列中節(jié)點(diǎn)添加的方式也很簡單晦譬,將上一尾節(jié)點(diǎn)的nextWaiter指向新添加的節(jié)點(diǎn)
疤苹,同時使lastWaiter
指向新添加的節(jié)點(diǎn)。
同步隊列與等待隊列的對應(yīng)關(guān)系
上文提到了整個Lock鎖機(jī)制需要AQS中的同步隊列
與ConditionObject的等待隊列
配合使用敛腌,其對應(yīng)關(guān)系如下圖所示:
在Lock鎖機(jī)制下卧土,可以擁有一個同步隊列和多個等待隊列
,與我們傳統(tǒng)的Object監(jiān)視器模型上像樊,一個對象擁有一個同步隊列和等待隊列不同尤莺。lock中的鎖可以伴有多個條件。
Condition的基本使用
為了大家能夠更好的理解同步隊列與等待隊列的關(guān)系生棍。下面通過一個有界隊列BoundedBuffer
來了解Condition的使用方式颤霎,該類是一個特殊的隊列,當(dāng)隊列為空時,隊列的獲取操作將會阻塞當(dāng)前"拿"線程捷绑,直到隊列中有新增的元素,當(dāng)隊列已滿時氢妈,隊列的放入操作將會阻塞"放入"線程粹污,直到隊列中出現(xiàn)空位。具體代碼如下所示:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
//依次為首量,放入的角標(biāo)壮吩、拿的角標(biāo)、數(shù)組中放入的對象總數(shù)
int putptr, takeptr, count;
/**
* 添加一個元素
* (1)如果當(dāng)前數(shù)組已滿加缘,則把當(dāng)前"放入"線程鸭叙,加入到"放入"等待隊列中,并阻塞當(dāng)前線程
* (2)如果當(dāng)前數(shù)組未滿拣宏,則將x元素放入數(shù)組中沈贝,喚醒"拿"線程中的等待線程。
*/
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)//如果已滿勋乾,則阻塞當(dāng)前"放入"線程
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();//喚醒"拿"線程
} finally {
lock.unlock();
}
}
/**
* 拿一個元素
* (1)如果當(dāng)前數(shù)組已空宋下,則把當(dāng)前"拿"線程,加入到"拿"等待隊列中辑莫,并阻塞當(dāng)前線程
* (2)如果當(dāng)前數(shù)組不為空学歧,則把喚醒"放入"等待隊列中的線程。
*/
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)//如果為空各吨,則阻塞當(dāng)前"拿"線程
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();//喚醒"放入"線程
return x;
} finally {
lock.unlock();
}
}
}
從代碼中我們可以看出枝笨,在該類中我們創(chuàng)建了兩個等待隊列notFull
與notEmpty
。這兩個等待隊列的作用分別是揭蜒,當(dāng)請數(shù)組已滿時横浑,notFull
用于存儲阻塞的"放入"線程,notEmpty
用于存儲阻塞的"拿"線程忌锯。需要注意的是獲取一個Condition必須通過Lock的newCondition()方法
伪嫁。關(guān)于ReentrantLock
,在后續(xù)的文章中偶垮,我們會進(jìn)行介紹张咳。
阻塞實(shí)現(xiàn) await()
在了解了ConditionObject的內(nèi)部基本結(jié)構(gòu)和與AQS中內(nèi)部的同步隊列的對應(yīng)關(guān)系后,現(xiàn)在我們來看看其阻塞實(shí)現(xiàn)似舵。調(diào)用ConditionObject的await()
方法(或者以await開頭
的方法)脚猾,會使當(dāng)前線程進(jìn)入等待隊列,并釋放同步狀態(tài)砚哗,需要注意的是當(dāng)該方法返回時龙助,當(dāng)前線程一定獲取了同步狀態(tài)(具體原因是當(dāng)通過signal()等系列方法
,線程才會從await()
方法返回,而喚醒該線程后會加入同步隊列)提鸟。這里我們以awati()
方法為例军援,具體代碼如下所示:
public final void await() throws InterruptedException {
//如果當(dāng)前線程已經(jīng)中斷,直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
//(1)將當(dāng)前線程加入等待隊列
Node node = addConditionWaiter();
//(2)釋放同步狀態(tài)(也就是釋放鎖)称勋,同時將線程節(jié)點(diǎn)從同步隊列中移除胸哥,并喚醒同步隊列中的下一節(jié)點(diǎn)
int savedState = fullyRelease(node);
int interruptMode = 0;
//(3)判斷當(dāng)前線程節(jié)點(diǎn)是否還在同步隊列中,如果不在則阻塞線程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//(4)當(dāng)線程被喚醒后赡鲜,重新在同步隊列中與其他線程競爭獲取同步狀態(tài)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
從代碼整體來看空厌,整個方法分為以下四個步驟:
- (1)通過
addConditionWaiter()
方法將線程節(jié)點(diǎn)加入到等待隊列中。 - (2)通過
fullyRelease(Node node)
方法釋放同步狀態(tài)(也就是釋放鎖)银酬,同時將線程節(jié)點(diǎn)從同步隊列
中移除嘲更,并喚醒同步隊列中的下一節(jié)點(diǎn)
。 - (3)通過
isOnSyncQueue(Node node)
方法判斷當(dāng)前線程節(jié)點(diǎn)是否在同步隊列
中揩瞪,如果不在赋朦,則通過LockSupport.park(this);
阻塞當(dāng)前線程。 - (4)當(dāng)線程被喚醒后李破,調(diào)用
acquireQueued(node, savedState)
方法北发,重新在同步隊列中與其他線程競爭獲取同步狀態(tài)
因?yàn)槊總€步驟涉及到的邏輯都稍微有一點(diǎn)復(fù)雜,這里為了方便大家理解喷屋,分別對以上四個步驟涉及到的方法分別進(jìn)行介紹琳拨。
addConditionWaiter()方法
該方法主要將同步隊列中的需要阻塞的線程節(jié)點(diǎn)加入到等待隊列中,關(guān)于addConditionWaiter()
方法具體代碼如下所示:
private Node addConditionWaiter() {
Node t = lastWaiter;
// (1)如果當(dāng)前尾節(jié)點(diǎn)中中對應(yīng)的線程已經(jīng)中斷屯曹,
//則移除等待隊列中所有的已經(jīng)中斷或已經(jīng)釋放同步狀態(tài)的線程節(jié)點(diǎn)
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//(2)構(gòu)建等待隊列中的節(jié)點(diǎn)
Node node = new Node(Node.CONDITION);
//(3)將該線程節(jié)點(diǎn)添加到隊列中狱庇,同時構(gòu)建firstWaiter與lastWaiter的指向
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
該方法的邏輯也比較簡單,分為以下三個步驟:
- (1)獲取等待隊列中的尾節(jié)點(diǎn)恶耽,如果當(dāng)前尾節(jié)點(diǎn)已經(jīng)中斷密任,那么則通過
unlinkCancelledWaiters()
方法移除等待隊列中所有的已經(jīng)中斷
或已經(jīng)釋放同步狀態(tài)(也就是釋放鎖)
的線程節(jié)點(diǎn) - (2)構(gòu)建等待隊列中的節(jié)點(diǎn),注意偷俭,是通過
New
的形式浪讳,那么就說明與同步隊列中的線程節(jié)點(diǎn)不是同一個。(對Node狀態(tài)枚舉不清楚的小伙伴涌萤,可以參看Java并發(fā)編程之鎖機(jī)制之AQS文章下的Node狀態(tài)枚舉介紹)淹遵。 - (3)將該線程節(jié)點(diǎn)添加到等待隊列中去,同時構(gòu)建firstWaiter與lastWaiter的指向负溪,可以看出等待隊列總是以
FIFO(first in first out )
的形式添加線程節(jié)點(diǎn)透揣。
unlinkCancelledWaiters()方法
因?yàn)樵?code>addConditionWaiter()方法的步驟(1)中,調(diào)用了unlinkCancelledWaiters
移除了所有的已經(jīng)中斷
的線程節(jié)點(diǎn)川抡,那我們看一個該方法的實(shí)現(xiàn)辐真。如下所示:
private void unlinkCancelledWaiters() {
//獲取等待隊列中的頭節(jié)點(diǎn)
Node t = firstWaiter;
Node trail = null;
//遍歷等待隊列,將已經(jīng)中斷的線程節(jié)點(diǎn)從等待隊列中移除。
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)//重新定義lastWaiter的指向
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
該方法具體流程如下圖所示:
fullyRelease(Node node)
在將阻塞線程加入到等待隊列后侍咱,會將該線程節(jié)點(diǎn)從同步隊列中移除耐床,釋放同步狀態(tài)(也就是釋放鎖),并喚醒同步隊列中的下一節(jié)點(diǎn)楔脯。具體代碼如下所示:
final int fullyRelease(Node node) {
try {
int savedState = getState();
if (release(savedState))
return savedState;
throw new IllegalMonitorStateException();
} catch (Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
}
release(int arg)
方法會釋放當(dāng)前線程的同步狀態(tài)咙咽, 并喚醒同步隊列中
的下一線程節(jié)點(diǎn),使其嘗試獲取同步狀態(tài)淤年,因?yàn)樵摲椒ㄒ呀?jīng)在Java并發(fā)編程之鎖機(jī)制之AQS文章下的unparkSuccessorNode node)
方法的下分析過了,所以這里就不再進(jìn)行分析了蜡豹。希望大家參考上面提到的文章進(jìn)行理解麸粮。
isOnSyncQueue(Node node)
該方法主要用于判斷當(dāng)前線程節(jié)點(diǎn)是否在同步隊列中。具體代碼如下所示:
final boolean isOnSyncQueue(Node node) {
//判斷當(dāng)前節(jié)點(diǎn) waitStatus ==Node.CONDITION或者當(dāng)前節(jié)點(diǎn)上一節(jié)點(diǎn)為空,則不在同步隊列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果當(dāng)前節(jié)點(diǎn)擁有下一個節(jié)點(diǎn)镜廉,則在同步隊列中弄诲。
if (node.next != null) // If has successor, it must be on queue
return true;
//如果以上條件都不滿足,則遍歷同步隊列娇唯。檢查是否在同步隊列中齐遵。
return findNodeFromTail(node);
}
如果你還記得AQS中的同步隊列,那么你應(yīng)該知道同步隊列中的Node節(jié)點(diǎn)才會使用其內(nèi)部的pre
與next
字段塔插,那么在同步隊列中因?yàn)橹皇褂昧?code>nextWaiter字段梗摇,所以我們就能很簡單的通過這兩個字段是否為==null
,來判斷是否在同步隊列中想许。當(dāng)然也有可能有一種特殊情況伶授。有可能需要阻塞的線程節(jié)點(diǎn)還沒有加入到同步隊列中,那么這個時候我們需要遍歷同步隊列來判斷是該線程節(jié)點(diǎn)是否已存在流纹。具體代碼如下所示:
private boolean findNodeFromTail(Node node) {
for (Node p = tail;;) {
if (p == node)
return true;
if (p == null)
return false;
p = p.prev;
}
}
這里之所以使用同步隊列tail(尾節(jié)點(diǎn))
來遍歷糜烹,如果node.netx!=null
,那么就說明當(dāng)前線程已經(jīng)在同步隊列中漱凝。那么我們需要處理的情況肯定是針對node.next==null
的情況疮蹦。所以需要從尾節(jié)點(diǎn)開始遍歷。
acquireQueued(final Node node, int arg)
當(dāng)線程被喚醒后(具體原因是當(dāng)通過signal()等系列方法
茸炒,線程才會從await()
方法返回)會調(diào)用該方法將該線程節(jié)點(diǎn)加入到同步隊列中愕乎。該方法我在《Java并發(fā)編程之鎖機(jī)制之AQS》中具體描述過了。這里就不在進(jìn)行過多的解析壁公。
阻塞流程
在理解了整個阻塞的流程后妆毕,現(xiàn)在我們來歸納總結(jié)一下,整個阻塞的流程贮尖。具體流程如下圖所示:
- (1)將該線程節(jié)點(diǎn)從同步隊列中移除笛粘,并釋放其同步狀態(tài)。
- (2)構(gòu)造新的阻塞節(jié)點(diǎn),加入到等待隊列中薪前。
喚醒實(shí)現(xiàn) signal()
當(dāng)需要喚醒線程時润努,會調(diào)用ConditionObject中的singal開頭的系列方法
,該系列方法會喚醒等待隊列中的首個
線程節(jié)點(diǎn)示括,在喚醒該節(jié)點(diǎn)之前铺浇,會先講該節(jié)點(diǎn)移動到同步隊列
中。這里我們以singal()
方法為例進(jìn)行講解垛膝,具體代碼如下:
public final void signal() {
//(1)判斷當(dāng)前線程是否獲取到了同步狀態(tài)(也就是鎖)
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//(2)獲取等待隊列中的首節(jié)點(diǎn)鳍侣,然后將其移動到同步隊列,然后再喚醒該線程節(jié)點(diǎn)
if (first != null)
doSignal(first);
}
該方法主要邏輯分為以下兩個步驟:
- (1)通過
isHeldExclusively()
方法吼拥,判斷當(dāng)前線程是否獲取到了同步狀態(tài)(也就是鎖)倚聚。 - (2)通過
doSignal(Node first)
方法,獲取等待隊列中的首節(jié)點(diǎn)凿可,然后將其移動到同步隊列惑折,然后再喚醒該線程節(jié)點(diǎn)。
下面我們會分別對上面涉及到的兩個方法進(jìn)行描述枯跑。
isHeldExclusively()方法
isHeldExclusively()
方法是AQS中的方法惨驶,默認(rèn)交給其子類實(shí)現(xiàn),主要用于判斷當(dāng)前調(diào)用singal()
方法的線程敛助,是否在同步隊列中粗卜,且已經(jīng)獲取了同步狀態(tài)。具體代碼如下所示:
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
doSignal(Node first)方法
那我們繼續(xù)跟蹤doSignal(Node first)
方法纳击,具體方法如下:
private void doSignal(Node first) {
do {
//(1)將等待隊列中的首節(jié)點(diǎn)從等待隊列中移除休建,并重新制定firstWaiter的指向
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
//(2)將等待隊列中的首節(jié)點(diǎn),加入同步隊列中评疗,并重新喚醒該節(jié)點(diǎn)
(first = firstWaiter) != null);
}
該方法也很簡單测砂,分為兩個步驟:
-
(1)將等待隊列中的首節(jié)點(diǎn)從等待隊列中移除,并設(shè)置firstWaiter的指向?yàn)槭坠?jié)點(diǎn)的下一個節(jié)點(diǎn)百匆。 為了方便大家理解該步驟所描述的邏輯砌些,這里畫了具體的圖,具體情況如下圖所示:
移除首節(jié)點(diǎn).png - (2)通過
transferForSignal(Node node)
方法加匈,將等待隊列中的首節(jié)點(diǎn)存璃,加入到同步隊列中去,然后重新喚醒該線程節(jié)點(diǎn)雕拼。
transferForSignal(Node node)方法
因?yàn)椴襟E(2)中transferForSignal(Node node)
方法較為復(fù)雜纵东,所以會對該方法進(jìn)行詳細(xì)的講解。具體代碼如下所示:
final boolean transferForSignal(Node node) {
//(1)將該線程節(jié)點(diǎn)的狀態(tài)設(shè)置為初始狀態(tài)啥寇,如果失敗則表示當(dāng)前線程已經(jīng)中斷了
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
return false;
//(2)將該節(jié)點(diǎn)放入同步隊列中偎球,
Node p = enq(node);
int ws = p.waitStatus;
//(3)獲取當(dāng)前節(jié)點(diǎn)的狀態(tài)并判斷洒扎,嘗試將該線程節(jié)點(diǎn)狀態(tài)設(shè)置為Singal,如果失敗則喚醒線程
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
該方法分為三個步驟:
- (1)將該線程節(jié)點(diǎn)的狀態(tài)設(shè)置為初始狀態(tài)衰絮,如果失敗則表示當(dāng)前線程已經(jīng)中斷了袍冷,直接返回。
- (2)通過
enq(Node node)
方法猫牡,將該線程節(jié)點(diǎn)放入同步隊列
中胡诗。 - (3)當(dāng)將該線程節(jié)點(diǎn)放入同步隊列后,獲取當(dāng)前節(jié)點(diǎn)的狀態(tài)并判斷淌友,如果該節(jié)點(diǎn)的
waitStatus>0
或者通過compareAndSetWaitStatus(ws, Node.SIGNAL)
將該節(jié)點(diǎn)的狀態(tài)設(shè)置為Singal煌恢,如果失敗則通過LockSupport.unpark(node.thread)
喚醒線程。
上述步驟中震庭,著重講enq(Node node)
方法瑰抵,關(guān)于LockSupport.unPark(Thread thread)
方法的理解,大家可以閱讀《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》归薛。下面我們就來分析enq(Node node)
方法。具體代碼如下所示:
private Node enq(Node node) {
for (;;) {
//(1)獲取同步隊列的尾節(jié)點(diǎn)
Node oldTail = tail;
//(2)如果尾節(jié)點(diǎn)不為空匪蝙,則將該線程節(jié)點(diǎn)加入到同步隊列中
if (oldTail != null) {
//將當(dāng)前節(jié)點(diǎn)的prev指向尾節(jié)點(diǎn)
U.putObject(node, Node.PREV, oldTail);
//將同步隊列中的tail指針主籍,指向當(dāng)前節(jié)點(diǎn)
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return oldTail;
}
} else {
//(3)如果當(dāng)前同步隊列為空,則構(gòu)造同步隊列
initializeSyncQueue();
}
}
}
觀察該方法逛球,我們發(fā)現(xiàn)該方法通過死循環(huán)(當(dāng)然你也可以叫做自旋)
的方式來添加該節(jié)點(diǎn)到同步隊列中去千元。該方法分為以下步驟:
- (1)獲取同步隊列的尾節(jié)點(diǎn)
- (2)如果尾節(jié)點(diǎn)不為空,則將該線程節(jié)點(diǎn)加入到同步隊列中
- (3)如果當(dāng)前同步隊列為空颤绕,則通過
initializeSyncQueue();
構(gòu)造同步隊列幸海。
這里對Node enq(Node node)
中的步驟(2)補(bǔ)充一個知識點(diǎn)。我們來看一下調(diào)用U.putObject(node, Node.PREV, oldTail);
語句奥务,內(nèi)部是如何將當(dāng)前的節(jié)點(diǎn)的prev指向尾節(jié)點(diǎn)的物独。在AQS(AbstractQueuedSynchronizer)
中的Node類中有如下靜態(tài)變量和語句。這里我省略了一下不重要的代碼氯葬。具體代碼如下所示:
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
//省略部分代碼
static final long PREV;
static {
try {
//省略部分代碼
PREV = U.objectFieldOffset
(Node.class.getDeclaredField("prev"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
其中Node.class.getDeclaredField("prev")
語句很好理解挡篓,就是獲取Node類中pre
字段,如果有則返回相應(yīng)Field字段帚称,反之拋出NoSuchFieldException異常官研。關(guān)于Unfase中的objectFieldOffset(Field f)
方法,我曾經(jīng)在《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》描述過類似的情況闯睹。這里我簡單的再解釋一遍戏羽。該方法用于獲取某個字段相對 Java對象的“起始地址”的偏移量,也就是說每個字段在類對應(yīng)的內(nèi)存中存儲是有“角標(biāo)”
的楼吃,那么也就是說我們現(xiàn)在的PREV
靜態(tài)變量就代表著Node中prev
字段在內(nèi)存中的“角標(biāo)”始花。
當(dāng)獲取到"角標(biāo)"后妄讯,我們再通過U.putObject(node, Node.PREV, oldTail);
該方法第一個參數(shù)是操作對象,第二個參數(shù)是操作的內(nèi)存“角標(biāo)”衙荐,第三個參數(shù)是期望值捞挥。那么最后,也就完成了將當(dāng)前節(jié)點(diǎn)的prev字段指向同步隊列的尾節(jié)點(diǎn)忧吟。
當(dāng)理解了該知識點(diǎn)后砌函,剩下的將同步隊列中的tail指針,指向當(dāng)前節(jié)點(diǎn)
與如果當(dāng)前同步隊列為空溜族,則構(gòu)造同步隊列
這兩個操作就非常好理解了讹俊。由于篇幅的限制,在這里我就不在進(jìn)行描述了煌抒。希望讀者朋友們仍劈,能閱讀源代碼,舉一反三寡壮。關(guān)于這兩個方法的代碼如下所示:
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long STATE;
private static final long HEAD;
private static final long TAIL;
static {
try {
STATE = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
HEAD = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
TAIL = U.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
Class<?> ensureLoaded = LockSupport.class;
}
private final void initializeSyncQueue() {
Node h;
if (U.compareAndSwapObject(this, HEAD, null, (h = new Node())))
tail = h;
}
private final boolean compareAndSetTail(Node expect, Node update) {
return U.compareAndSwapObject(this, TAIL, expect, update);
}
喚醒流程
在理解了喚醒的具體邏輯后贩疙,現(xiàn)在來總結(jié)一下,喚醒的具體流程况既。具體如下圖所示:
- 將等待隊列中的
頭
節(jié)點(diǎn)線程这溅,移動到同步隊列中。 - 當(dāng)移動到同步隊列中后棒仍。喚醒該線程悲靴。讓該線程參與同步狀態(tài)的競爭。
整體流程其實(shí)不算太復(fù)雜莫其,大家只需要注意癞尚,當(dāng)我們將等待隊列中的線程節(jié)點(diǎn)加入到同步隊列之后,才會喚醒線程
乱陡。
最后
該文章參考以下圖書浇揩,站在巨人的肩膀上『┑撸可以看得更遠(yuǎn)临燃。
- 《Java并發(fā)編程的藝術(shù)》