0抡诞、引言
作為并發(fā)相關(guān)內(nèi)容的第二篇洽瞬,這里主要延續(xù)上一篇文章《Java并發(fā)源碼剖析(一)——AbstractQueuedSynchronizer獨(dú)占模式》的內(nèi)容繼續(xù)介紹共享模式的知識相叁。
1敞映、共享式AQS
獨(dú)占式AQS主要是運(yùn)用在ReentrantLock內(nèi)朱嘴,而共享式AQS主要是Semaphore返奉、CountDownLatch变逃。它旨在提供一個可以同時通過多個線程的阻塞方式必逆。獨(dú)占式是一種悲觀鎖,而共享式是一種樂觀鎖揽乱。這也是它與獨(dú)占式AQS最大的區(qū)別名眉。
2、共享式的機(jī)制
2.1凰棉、acquire共享模式
共享式的acquire操作與獨(dú)占式的差別不太大损拢,也是調(diào)用子類的tryAcquireShared方法,如果失敗后才進(jìn)行后續(xù)的操作撒犀。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
2.1.1福压、doAcquireShared
總的來說,兩者的代碼思路是一致的或舞,我將兩個代碼放在一起供大家做一個對比荆姆。如果有不是很了解的可以參考我的第一篇文章。
這里主要指出一下不同映凳。
- addWaiter設(shè)置為shared模式胆筒。
- tryAcquire和tryAcquireShared的返回值不同,因此會多出一個判斷過程
- 在判斷前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)后诈豌,調(diào)用了setHeadAndPropagate方法腐泻,而不是簡單的更新一下頭節(jié)點(diǎn)决乎。(如圖中紅線標(biāo)出的地方)
2.1.2、setHeadAndPropagate
設(shè)置頭節(jié)點(diǎn)狀態(tài)派桩,并通過propagate判斷是否可以允許acquire
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate也就是state的更新值大于0构诚,代表可以繼續(xù)acquire
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 判斷后繼節(jié)點(diǎn)是否存在,如果存在是否是共享模式的節(jié)點(diǎn)
// 然后進(jìn)行共享模式的釋放
if (s == null || s.isShared())
doReleaseShared();
}
}
在這里h頭節(jié)點(diǎn)進(jìn)行了兩次判定铆惑,第一次是判定舊頭節(jié)點(diǎn)存在且狀態(tài)已經(jīng)被設(shè)置過范嘱,第二次是判定設(shè)置后的頭節(jié)點(diǎn)是否存在并且狀態(tài)已經(jīng)被設(shè)置過。只有滿足上述的一個條件员魏,就會對其后繼節(jié)點(diǎn)做判斷丑蛤。后繼節(jié)點(diǎn)不存在或者后繼節(jié)點(diǎn)是共享模式,那就可以對整個隊列進(jìn)行釋放操作撕阎。
Q1:這里為什么要判斷狀態(tài)為小于0的情況受裹,直接使用PROPAGATE狀態(tài)不可以嗎?
這個是因為PROPAGATE狀態(tài)是會被轉(zhuǎn)換為SIGNAL的虏束,這個在shouldParkAfterFailedAcquire方法中處理的棉饶,不清楚的可以再翻閱一下之前的文章。
這里有個小疑問镇匀,我也沒太想明白照藻??汗侵?
為什么要去對老節(jié)點(diǎn)的狀態(tài)去做一個判斷幸缕,源碼中注釋解釋的是在多重acquire/release情況下會出現(xiàn)不必要的喚醒。反正我是沒懂這是什么意思晰韵?
2.1.3发乔、doReleaseShared
從頭節(jié)點(diǎn)開始, 判斷頭節(jié)點(diǎn)后繼的狀態(tài)雪猪,來確定后繼需不需要喚醒列疗。
private void doReleaseShared() {
for (;;) {
Node h = head;
// 只需要處理頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都存在,且隊列內(nèi)的節(jié)點(diǎn)總數(shù)超過1個的情況
if (h != null && h != tail) {
int ws = h.waitStatus;
// 兩種模式下都需要SIGNAL信號來判斷是否喚醒后繼節(jié)點(diǎn)
if (ws == Node.SIGNAL) {
// 如果CAS操作失敗了就繼續(xù)循環(huán)處理
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
continue;
}
// CAS操作成功后浪蹂,就將后繼節(jié)點(diǎn)解除阻塞
unparkSuccessor(h);
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
continue;
}
// 當(dāng)狀態(tài)碼是PROPAGATE的時候抵栈,就可以結(jié)束循環(huán)了
}
// 在循環(huán)過程中,為了防止在上述操作過程中新添加了節(jié)點(diǎn)的情況坤次,
// 通過檢查頭節(jié)點(diǎn)是否改變了古劲,如果改變了就繼續(xù)循環(huán)
if (h == head)
break;
}
}
由于共享模式和獨(dú)占模式的隊列是一樣的,他們都需要區(qū)分出SIGNAL信號缰猴,然后對該后繼節(jié)點(diǎn)進(jìn)行阻塞的接觸产艾。對于狀態(tài)為初始值的節(jié)點(diǎn),就可以將其狀態(tài)設(shè)置為PROPAGATE,不做任何其他操作闷堡,結(jié)束循環(huán)隘膘。
這里的循環(huán)主要是檢測是否在上述操作中,有新的節(jié)點(diǎn)加入到隊列中杠览。
2.2弯菊、release共享模式
共享模式下的release操作與獨(dú)占式無太大差別,而核心方法還是在于doReleaseShared踱阿。具體方法內(nèi)容在2.4小節(jié)內(nèi)已經(jīng)解釋過管钳,可以返回到上一小節(jié)再分析一下。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
因為從頭節(jié)點(diǎn)開始的節(jié)點(diǎn)進(jìn)行處理软舌,所以對于共享模式的release來說才漆,一般來說,只需要判斷兩種情況佛点,一種SIGNAL代表后繼節(jié)點(diǎn)之前被阻塞了需要釋放醇滥,而另一種是PROPAGATE代表共享模式下可以繼續(xù)進(jìn)行acquire。
3超营、小結(jié)
共享式的操作與獨(dú)占式的主要區(qū)別在于鸳玩,每次acquire競爭失敗后,獨(dú)占式將立即阻塞當(dāng)前線程糟描,而共享式需要在多次acquire失敗后才會阻塞當(dāng)前線程怀喉。簡單來說书妻,共享式是有一定限額的獨(dú)占式船响。限額的滿足方式,根據(jù)不同的子類不同的實現(xiàn)方式躲履。