獲取共享式同步狀態(tài)
入口函數(shù)
/**
* 共享式獲取同步狀態(tài),如果當(dāng)前線程獲取同步狀態(tài)成功則直接返回,
* 如果獲取失敗則線程阻塞络凿,并插入同步隊(duì)列進(jìn)行.等待調(diào)用releaseShared
* 釋放同步狀態(tài)時(shí),重新嘗試獲取同步狀態(tài)虱朵。成功則,同時(shí)會(huì)通知后置節(jié)點(diǎn)線程從阻塞中喚醒,
* 獲取同步狀態(tài)并返回吧兔,失敗則阻塞等待下次release
*/
public final void acquireShared(int arg) {
/**
*子類實(shí)現(xiàn)tryAcquireShared能否獲取的共享式同步狀態(tài)
*如果返回>=0則獲取同步狀態(tài)成功方法直接返回
*如果返回< 0則獲取同步狀態(tài)失敗進(jìn)入if語(yǔ)句
*/
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
doAcquireShared
- 1 創(chuàng)建節(jié)點(diǎn)node(類型是共享式),添加到同步隊(duì)列尾部,默認(rèn)狀態(tài)為0 【同獨(dú)占式同步相同】
- 2 進(jìn)入自旋【同獨(dú)占式同步相同】
- 3 判斷當(dāng)前節(jié)點(diǎn)前置節(jié)點(diǎn)是否為head節(jié)點(diǎn),如果是重新嘗試獲取同步狀態(tài)【同獨(dú)占式同步相同】
- 4 如果同步狀態(tài)成功,設(shè)置當(dāng)前節(jié)點(diǎn)為head節(jié)點(diǎn).會(huì)判斷同步隊(duì)列中是否存在等待的共享節(jié)點(diǎn).如果存在則會(huì)調(diào)用doReleaseShared喚醒新head節(jié)點(diǎn)后置節(jié)點(diǎn)阻塞,被喚醒的節(jié)點(diǎn)線程重新進(jìn)入自旋袍嬉。【同獨(dú)占式同步不相同】
- 5 嘗試獲取同步狀態(tài)失敗,獲取同步狀態(tài)失敗,就將當(dāng)前節(jié)點(diǎn)和前驅(qū)節(jié)點(diǎn)作為參數(shù)交給shouldParkAfterFailedAcquire調(diào)用灶平,shouldParkAfterFailedAcquire設(shè)置要當(dāng)前節(jié)點(diǎn)前驅(qū)節(jié)點(diǎn)node等待狀態(tài)到-1返回false伺通,自旋第二次進(jìn)入時(shí)返回true,到parkAndCheckInterrupt()阻塞當(dāng)前線程》晗恚【同獨(dú)占式同步相同】
- 6 如果節(jié)點(diǎn)線程被從阻塞中喚醒重新進(jìn)入自旋 【同獨(dú)占式同步相同】
這里步驟4 表示了和獨(dú)占最大的不同:獨(dú)占同步的節(jié)點(diǎn)獲得同步狀態(tài)后罐监,直接退出同步隊(duì)列。而共享同步的節(jié)點(diǎn)會(huì)通知自己后置的同步節(jié)點(diǎn)線程(后置也是共享節(jié)點(diǎn))從阻塞中喚醒去嘗試獲取同步狀態(tài)瞒爬,如果獲取同步狀態(tài)成功弓柱,則表示2個(gè)線程獲得了一把鎖(一次釋放動(dòng)作表示一把鎖),同時(shí)執(zhí)行侧但。同時(shí)第2個(gè)線程的節(jié)點(diǎn)同樣會(huì)通知后置同伴節(jié)點(diǎn)線程從阻塞中喚醒并嘗試獲取同步狀態(tài)矢空。
/**
* 創(chuàng)建一個(gè)共享式節(jié)點(diǎn)node,添加到同步隊(duì)列尾部.
* 進(jìn)入自旋,找到CLH頭部后置第一個(gè)節(jié)點(diǎn),嘗試獲取同步狀態(tài),成功則設(shè)置其為新head節(jié)點(diǎn)禀横,
* 并通知后置節(jié)點(diǎn)線程從阻塞中喚醒競(jìng)爭(zhēng)同步狀態(tài).失敗則阻塞.
*/
private void doAcquireShared(int arg) {
/** 創(chuàng)建一個(gè)共享式節(jié)點(diǎn)node,添加到同步隊(duì)列尾部..**/
final Node node = addWaiter(Node.SHARED);
/** 執(zhí)行是否發(fā)生異常 **/
boolean failed = true;
try {
/** 標(biāo)識(shí)是否被中斷 **/
boolean interrupted = false;
/** 進(jìn)入自旋 **/
for (;;) {
/** 1. 獲得當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn) **/
final Node p = node.predecessor();
if (p == head) {
/** 如果當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲取同步狀態(tài) **/
int r = tryAcquireShared(arg);
if (r >= 0) {
/** 將當(dāng)前節(jié)點(diǎn)設(shè)置為head,同時(shí)只要同步隊(duì)列中存在等待的節(jié)點(diǎn),
* 且節(jié)點(diǎn)為共享節(jié)點(diǎn)則喚醒head節(jié)點(diǎn)后置節(jié)點(diǎn)阻塞去競(jìng)爭(zhēng)同步狀態(tài). **/
setHeadAndPropagate(node, r);
p.next = null; // help GC
/** 如果當(dāng)前線程中斷 **/
if (interrupted)
/** 中斷當(dāng)前線程 **/
selfInterrupt();
failed = false;
return;
}
}
/** 獲取鎖失敗屁药,在shouldParkAfterFailedAcquire中設(shè)置節(jié)點(diǎn)的等待狀態(tài),并線程阻塞(可響應(yīng)線程被中斷),
* 如果是中斷響應(yīng)設(shè)置interrupted = true;
* 重新進(jìn)入自旋**/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
/** 發(fā)生異常柏锄,將當(dāng)前節(jié)點(diǎn)等待狀態(tài)設(shè)置為取消**/
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
doReleaseShared
1 內(nèi)部通過(guò)一個(gè)自旋酿箭,找到Head節(jié)點(diǎn)后置節(jié)點(diǎn)中線程從阻塞狀態(tài)中喚醒競(jìng)爭(zhēng)同步狀態(tài).
2 同時(shí)會(huì)檢測(cè)釋放節(jié)點(diǎn)的線程是否會(huì)獲取同步狀態(tài),如果檢查獲取同步狀態(tài)成功趾娃,則在次釋放節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)中的線程從阻塞中喚醒缭嫡,重復(fù)迭代(是多線程可能釋放的線程還沒(méi)來(lái)的及獲取同步狀態(tài)。同步隊(duì)列head節(jié)點(diǎn)沒(méi)有改變退出的情況也是存在)因而此出邏輯作用是為性能考慮抬闷。
3 即使沒(méi)有檢測(cè)釋放釋放線程獲取到同步狀態(tài)返回妇蛀,也不會(huì)太大影響,因?yàn)槊總€(gè)線程獲取同步狀態(tài)時(shí)還是會(huì)調(diào)用setHeadAndPropagate檢查是否需要釋放自己下個(gè)節(jié)點(diǎn)的線程的阻塞饶氏。
private void doReleaseShared() {
//進(jìn)入自旋
for (;;) {
//獲取當(dāng)前head節(jié)點(diǎn),判斷等待隊(duì)列中是否存在等待節(jié)點(diǎn),不存在進(jìn)入步驟4直接退出.
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//如果當(dāng)前節(jié)點(diǎn)的狀態(tài)為-1(存在等待的后置節(jié)點(diǎn)),使用CAS設(shè)置其為0(使用CAS失敗進(jìn)入自旋重新設(shè)置)同時(shí)會(huì)喚醒head后置節(jié)點(diǎn)中線程從阻塞中釋放.
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//如果當(dāng)前節(jié)點(diǎn)的狀態(tài)為0(存在后置節(jié)點(diǎn)已經(jīng)取消等待),使用CAS設(shè)置其為PROPAGATE(使用CAS失敗進(jìn)入自旋重新設(shè)置),
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
/**
判斷喚醒線程是否獲取同步狀態(tài),獲取同步狀態(tài)同步隊(duì)列head節(jié)點(diǎn)引用會(huì)發(fā)生改變讥耗,則釋放被釋放節(jié)點(diǎn)后置節(jié)點(diǎn)的線程中阻塞中喚醒競(jìng)爭(zhēng)同步狀態(tài)
**/
if (h == head) // loop if head changed
break;
}
}
釋放共享式同步狀態(tài)
入口函數(shù)
releaseShared
public final boolean releaseShared(int arg) {
/*
*子類實(shí)現(xiàn)能否釋放的共享式同步狀態(tài)
*如果返回true則表示釋放同步狀態(tài)準(zhǔn)入條件成功進(jìn)入if語(yǔ)句
*如果返回false則表示釋放同步狀態(tài)失敗返回false
*/
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
總結(jié)
獨(dú)占式同步可以看作是一把共享鎖,每次可以多個(gè)線程獲的鎖疹启。
總體流程如下
嘗試鎖失敗 --> 進(jìn)入等待隊(duì)列排隊(duì) --> 阻塞當(dāng)前線程 --> 當(dāng)?shù)却?duì)列排到自己被喚醒 --> 嘗試獲取鎖(可能被其他線程插隊(duì)而導(dǎo)致獲取鎖失敗古程,失敗在次阻塞,等待下次排到自己)--> 成功通知等待隊(duì)列前面共享節(jié)點(diǎn)線程從阻塞中喚醒 --> 執(zhí)行自己的業(yè)務(wù)邏輯 --> 嘗試釋放鎖--> 成功通知等待隊(duì)列前面共享節(jié)點(diǎn)線程從阻塞中喚醒喊崖。
子類可以擴(kuò)展如何獲取鎖挣磨,如何釋放鎖雇逞,但整體流程不變。
白話
我們舉一個(gè)生活中例子來(lái)看獨(dú)占式同步茁裙,比如我們?nèi)ゲ蛷d吃過(guò)飯塘砸,餐廳吃飯這個(gè)動(dòng)作就是我們要執(zhí)行業(yè)務(wù),但要進(jìn)入餐廳吃飯的前提要獲取同步狀態(tài)晤锥。對(duì)于獨(dú)占來(lái)說(shuō)只有一個(gè)位置掉蔬,那么就獨(dú)占式同步,那么進(jìn)入餐廳這個(gè)動(dòng)作(獲取鎖)只能保證同時(shí)一個(gè)人吃飯(一個(gè)線程)矾瘾,離開(kāi)餐廳這個(gè)動(dòng)作(釋放鎖)也只能同時(shí)保證一個(gè)進(jìn)入(一個(gè)線程)女轿。那么對(duì)于共享式同步,餐廳多個(gè)位置壕翩,那么就是共享式同步蛉迹,因?yàn)橥蕉鄠€(gè)人進(jìn)入餐廳吃飯。當(dāng)然能不能進(jìn)入邏輯時(shí)餐廳這個(gè)AQS子類去定制的放妈。
我們來(lái)看共享式例子北救,此時(shí)餐廳共有2個(gè)位置。
客人A進(jìn)入餐廳吃飯芜抒,餐廳此時(shí)沒(méi)有人珍策,他可以順利進(jìn)入餐廳吃飯。
客人B進(jìn)入餐廳吃飯挽绩,餐廳還有位置膛壹,他可以順利進(jìn)入餐廳吃飯。
客戶C也進(jìn)入餐廳吃飯唉堪,餐廳此時(shí)被A占著模聋,嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷),進(jìn)入餐廳失敗
餐廳給C指定一個(gè)排隊(duì)編號(hào)唠亚,此時(shí)C想插隊(duì)链方,在次嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷),A,B還沒(méi)有出來(lái)灶搜,進(jìn)入餐廳失敗阻塞等待
客戶D也進(jìn)入餐廳吃飯祟蚀,餐廳此時(shí)被D占著,嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷)割卖,進(jìn)入餐廳失敗
餐廳給D指定一個(gè)排隊(duì)編號(hào)前酿,此時(shí)D想插隊(duì),在次嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷)鹏溯,A,B還沒(méi)有出來(lái)罢维,進(jìn)入餐廳失敗阻塞等待
A 吃飯完畢離開(kāi)餐廳,嘗試離開(kāi)餐廳(子類實(shí)現(xiàn)邏輯判斷)丙挽,成功餐廳通知C(喚醒C線程)肺孵,C嘗試進(jìn)入餐廳(可能還會(huì)被插隊(duì))匀借,成功,餐廳通知D(喚醒D線程),
D嘗試進(jìn)入餐廳平窘,B還沒(méi)有出來(lái)吓肋,進(jìn)入餐廳失敗阻塞等待
B 吃飯完畢離開(kāi)餐廳,嘗試離開(kāi)餐廳(子類實(shí)現(xiàn)邏輯判斷)瑰艘,成功餐廳通知D(喚醒D線程),D嘗試進(jìn)入餐廳是鬼,成功