1.獨(dú)占式同步狀態(tài)獲取
AQS提供了很多模板方法昆稿,模板方法中已經(jīng)定義好了各種行為辫封,只需要實(shí)現(xiàn)其中幾個(gè)關(guān)鍵的行為(接口),就可以復(fù)用整體的邏輯抹估,有較好的框架和復(fù)用性缠黍。
1.1 獲取同步執(zhí)行權(quán)-acquire
AQS底層是一個(gè)雙向隊(duì)列,也稱CLH隊(duì)列(其實(shí)就是仨人名)药蜻。當(dāng)獲取執(zhí)行權(quán)時(shí)瓷式,有兩種可能,獲取到了(皆大歡喜)语泽,沒獲取到(隊(duì)尾排隊(duì)去)贸典。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先嘗試獲取(==tryAcquire==)踱卵,如果獲取不到則嘗試入隊(duì)(==acquireQueued==廊驼,其中會(huì)自旋繼續(xù)嘗試獲取執(zhí)行權(quán))据过。
1.1.1 嘗試獲取同步執(zhí)行權(quán)-tryAcquire
其中的==tryAcquire==需要繼承方線程安全的實(shí)現(xiàn)獲取方法。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
1.1.2 嘗試入隊(duì)并自旋嘗試獲取執(zhí)行權(quán)-acquireQueued
嘗試入隊(duì)是通過==addWaiter==方法進(jìn)行的妒挎。完了以后就是在==acquireQueued==方法中绳锅,嘗試獲取執(zhí)行權(quán)。
問:AQS中什么時(shí)候能獲取到執(zhí)行權(quán)酝掩,當(dāng)封裝了==Thread==的節(jié)點(diǎn)信息排到隊(duì)首的時(shí)候鳞芙。
所以==acquireQueued==方法中就會(huì)自旋的檢查當(dāng)前節(jié)點(diǎn)到?jīng)]到隊(duì)首啊,沒到的話期虾,繼續(xù)block原朝。
1: final boolean acquireQueued(final Node node, int arg) {
2: // 記錄是否獲取同步狀態(tài)成功
3: boolean failed = true;
4: try {
5: // 記錄過程中,是否發(fā)生線程中斷
6: boolean interrupted = false;
7: /*
8: * 自旋過程彻消,其實(shí)就是一個(gè)死循環(huán)而已
9: */
10: for (;;) {
11: // 當(dāng)前線程的前驅(qū)節(jié)點(diǎn)
12: final Node p = node.predecessor();
13: // 當(dāng)前線程的前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)竿拆,且同步狀態(tài)成功
14: if (p == head && tryAcquire(arg)) {
15: setHead(node);
16: p.next = null; // help GC
17: failed = false;
18: return interrupted;
19: }
20: // 獲取失敗,線程等待--具體后面介紹
21: if (shouldParkAfterFailedAcquire(p, node) &&
22: parkAndCheckInterrupt())
23: interrupted = true;
24: }
25: } finally {
26: // 獲取同步狀態(tài)發(fā)生異常宾尚,取消獲取丙笋。
27: if (failed)
28: cancelAcquire(node);
29: }
30: }
1.1.3 如何判斷是否要進(jìn)入block狀態(tài)?- shouldParkAfterFailedAcquire
入?yún)⑹钱?dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)
4~9 判斷前節(jié)點(diǎn)是不是已經(jīng)處于等待狀態(tài)(==Node.SIGNAL==)煌贴,如果前節(jié)點(diǎn)已經(jīng)處于等待狀態(tài)了御板,那就說明當(dāng)前節(jié)點(diǎn)更應(yīng)該處于等待狀態(tài),畢竟CLH隊(duì)列是一個(gè)FIFO的隊(duì)列牛郑,判斷完成怠肋,應(yīng)該block,返回淹朋。
10~18 如果前節(jié)點(diǎn)已經(jīng)被取消(==Node.CANCEL==)笙各,已經(jīng)取消的節(jié)點(diǎn)不應(yīng)該成為當(dāng)前節(jié)點(diǎn)是否應(yīng)該入隊(duì)的考慮條件,所以一直向前探础芍,探到第一個(gè)狀態(tài)為非取消的節(jié)點(diǎn)杈抢,然后返回不應(yīng)該block,下一次是否應(yīng)該block由下一次進(jìn)入==shouldParkAfterFailedAcquire==再?zèng)Q定仑性』搪ィ【todo,為什么不在本次調(diào)用就判斷诊杆?畢竟可以做到知道前節(jié)點(diǎn)的狀態(tài)歼捐。】
1: private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
2: // 獲得前一個(gè)節(jié)點(diǎn)的等待狀態(tài)
3: int ws = pred.waitStatus;
4: if (ws == Node.SIGNAL) // Node.SIGNAL
5: /*
6: * This node has already set status asking a release
7: * to signal it, so it can safely park.
8: */
9: return true;
10: if (ws > 0) { // Node.CANCEL
11: /*
12: * Predecessor was cancelled. Skip over predecessors and
13: * indicate retry.
14: */
15: do {
16: node.prev = pred = pred.prev;
17: } while (pred.waitStatus > 0);
18: pred.next = node;
19: } else { // 0 或者 Node.PROPAGATE
20: /*
21: * waitStatus must be 0 or PROPAGATE. Indicate that we
22: * need a signal, but don't park yet. Caller will need to
23: * retry to make sure it cannot acquire before parking.
24: */
25: compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
26: }
27: return false;
28: }
1.1.4 如何進(jìn)入block狀態(tài)晨汹? - parkAndCheckInterrupt
什么時(shí)候會(huì)調(diào)用這個(gè)方法豹储?首先調(diào)用==acquire==方法,嘗試獲取執(zhí)行權(quán)宰缤,如果失敗颂翼,會(huì)嘗試入隊(duì)==acquireQueued==晃洒,在入隊(duì)中,也在嘗試獲取執(zhí)行權(quán)朦乏,如果獲取失敗球及,會(huì)先調(diào)用==shouldParkAfterFailedAcquire==判斷當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的線程是否應(yīng)該block,如果應(yīng)該呻疹,則調(diào)用==parkAndCheckInterrupt==對(duì)線程進(jìn)行block處理吃引。
看下源碼實(shí)際執(zhí)行邏輯
首先調(diào)用==LockSupport.park==將當(dāng)前線程block」舸福【todo LockSupport.park的原理】
然后當(dāng)前線程是否被被打斷過镊尺,==Thread.interrupted==〔⑺迹【todo interrupted標(biāo)志位有什么用庐氮?】
正常情況下,會(huì)有兩種被打斷的情況
- 前節(jié)點(diǎn)釋放執(zhí)行權(quán)利宋彼,喚醒當(dāng)前節(jié)點(diǎn)弄砍。
- 當(dāng)前線程被打斷導(dǎo)致喚醒∈涮椋【todo 不甚理解這句話音婶,和前一個(gè)todo一起理解】
1.1.5 遇到異常情況,如何取消入隊(duì)操作 - cancelAcquire
入隊(duì)發(fā)生異常時(shí)莱坎,調(diào)用cancelAcquire(Node node)衣式,node為想要入隊(duì)的節(jié)點(diǎn)。目的是取消當(dāng)前節(jié)點(diǎn)的入隊(duì)操作檐什,并且當(dāng)前節(jié)點(diǎn)從同步隊(duì)列中刪除碴卧。
當(dāng)前節(jié)點(diǎn)分三種狀態(tài),隊(duì)首乃正,隊(duì)尾和隊(duì)中螟深。位于隊(duì)尾和隊(duì)中的節(jié)點(diǎn),將自己從隊(duì)中刪除即可烫葬。但是涉及到隊(duì)首的節(jié)點(diǎn),隊(duì)首的節(jié)點(diǎn)有什么特殊意義呢凡蜻?排到隊(duì)首的節(jié)點(diǎn)自動(dòng)獲取當(dāng)前同步狀態(tài)的執(zhí)行權(quán)利搭综,所以不能簡(jiǎn)單的還將自己刪除,還需要將執(zhí)行權(quán)利向下傳遞划栓。這也就是24~26(隊(duì)首)兑巾,29~36(隊(duì)中)和38(隊(duì)首)代碼的含義。具體的傳遞執(zhí)行權(quán)利的邏輯還需要看 1.1.6 unparkSuccessor忠荞。
3行 常規(guī)的判空操作
6行 將要?jiǎng)h除的節(jié)點(diǎn)對(duì)應(yīng)的線程置空 【todo蒋歌,為什么要置空帅掘,是不是其他調(diào)用處用到了==thread == null==去做已刪除節(jié)點(diǎn)的判斷】
9~11行 跳過已經(jīng)處于取消狀態(tài)的節(jié)點(diǎn),前探到第一個(gè)非取消狀態(tài)的節(jié)點(diǎn)堂油。
16行 獲取當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)修档。看下注釋府框,==predNext==引用指向的節(jié)點(diǎn)是明顯的第一個(gè)不用再跳過的節(jié)點(diǎn)吱窝,如果不是的化,下面的CAS操作將會(huì)失敗迫靖。失敗的原因是另一個(gè)線程比我們執(zhí)行的快院峡,它可能提前進(jìn)行了取消或者通知操作。所以在這步系宜,不需要額外的操作照激。
21行 將當(dāng)前節(jié)點(diǎn)狀態(tài)置成取消狀態(tài)。下面的刪除操作有可能執(zhí)行不到盹牧,所以需要將當(dāng)前節(jié)點(diǎn)狀態(tài)置成取消狀態(tài)俩垃,這樣的話,其他節(jié)點(diǎn)操作時(shí)欢策,可以憑借此狀態(tài)吆寨,判斷當(dāng)前節(jié)點(diǎn)不需要再被執(zhí)行,繼而跳過當(dāng)前節(jié)點(diǎn)踩寇。
24~26 如果當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn)啄清,則使用CAS,將前節(jié)點(diǎn)的==next域==置null俺孙。這里就用到了第16行的邏輯辣卒,使用CAS保證的是當(dāng)其他線程已經(jīng)更改了當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)的話,這里將會(huì)失敗睛榄,然后退出==cancelAcquire方法==荣茫。繼而使用第21行的邏輯,其他節(jié)點(diǎn)執(zhí)行操作時(shí)场靴,會(huì)憑借==Node.CANCELLED==狀態(tài)啡莉,跳過當(dāng)前節(jié)點(diǎn)。
27~36 刪除隊(duì)中節(jié)點(diǎn)旨剥∵中溃【todo,看下30~33行的邏輯轨帜,為什么當(dāng)前節(jié)點(diǎn)要被取消了魄咕,就要改變前節(jié)點(diǎn)的ws?只要<=0就要改成==Node.Signal==蚌父,我推測(cè)應(yīng)該是ws之間有正確的流轉(zhuǎn)狀態(tài)哮兰,這個(gè)很重要毛萌,要探索出來『戎停】
36行 然后給前節(jié)點(diǎn)和后節(jié)點(diǎn)做橋阁将,將二者關(guān)聯(lián)起來。相當(dāng)于將自己從隊(duì)列中刪除掉了囤躁。
33行處于29~36(隊(duì)中)冀痕,但是是為了防止誤刪隊(duì)首節(jié)點(diǎn),導(dǎo)致執(zhí)行權(quán)利無法向下傳遞的保證狸演。因?yàn)榭赡荛_始執(zhí)行29行時(shí)言蛇,當(dāng)前節(jié)點(diǎn)還是隊(duì)中,但隨著執(zhí)行宵距,當(dāng)前節(jié)點(diǎn)可能就被消費(fèi)到了隊(duì)首的位置腊尚,只有隊(duì)首節(jié)點(diǎn)的==Node.Thread==才有可能為null。
38行 隊(duì)首節(jié)點(diǎn)對(duì)應(yīng)的分支满哪,喚醒繼任者婿斥,具體的邏輯看1.1.6 unparkSuccessor。
看下源碼
1: private void cancelAcquire(Node node) {
2: // Ignore if node doesn't exist
3: if (node == null)
4: return;
5:
6: node.thread = null;
7:
8: // Skip cancelled predecessors
9: Node pred = node.prev;
10: while (pred.waitStatus > 0)
11: node.prev = pred = pred.prev;
12:
13: // predNext is the apparent node to unsplice. CASes below will
14: // fail if not, in which case, we lost race vs another cancel
15: // or signal, so no further action is necessary.
16: Node predNext = pred.next;
17:
18: // Can use unconditional write instead of CAS here.
19: // After this atomic step, other Nodes can skip past us.
20: // Before, we are free of interference from other threads.
21: node.waitStatus = Node.CANCELLED;
22:
23: // If we are the tail, remove ourselves.
24: if (node == tail && compareAndSetTail(node, pred)) {
25: compareAndSetNext(pred, predNext, null);
26: } else {
27: // If successor needs signal, try to set pred's next-link
28: // so it will get one. Otherwise wake it up to propagate.
29: int ws;
30: if (pred != head &&
31: ((ws = pred.waitStatus) == Node.SIGNAL ||
32: (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
33: pred.thread != null) {
34: Node next = node.next;
35: if (next != null && next.waitStatus <= 0)
36: compareAndSetNext(pred, predNext, next);
37: } else {
38: unparkSuccessor(node);
39: }
40:
41: node.next = node; // help GC
42: }
43: }
1.1.6 喚醒執(zhí)行權(quán)利的繼承者 - unparkSuccessor
【todo哨鸭,將waitstutas置為0民宿,0的含義是什么,在cancelAcquire中像鸡,刪除隊(duì)中節(jié)點(diǎn)時(shí)(29~36)活鹰,也判斷了0】
首先判斷當(dāng)前當(dāng)前節(jié)點(diǎn)的狀態(tài),如果<0只估,意味著當(dāng)前節(jié)點(diǎn)的狀態(tài)還為存活狀態(tài)志群,因?yàn)橹挥斜蝗∠墓?jié)點(diǎn)狀態(tài),才置為1蛔钙。然后將當(dāng)前節(jié)點(diǎn)狀態(tài)置為0锌云,0我理解是已經(jīng)獲取過執(zhí)行權(quán)利的節(jié)點(diǎn)狀態(tài)值。
隨后喚醒下一個(gè)可被喚醒的節(jié)點(diǎn)吁脱,已經(jīng)取消的節(jié)點(diǎn)會(huì)被跳過【todo桑涎,不會(huì)存在 可用-不可用-可用-不可用的節(jié)點(diǎn)順序嗎?】
最終調(diào)用==LockSupport.unpark==對(duì)后繼結(jié)點(diǎn)進(jìn)行喚醒操作兼贡∈矗【todo,LockSupport原理】
private void unparkSuccessor(Node node) {
//當(dāng)前節(jié)點(diǎn)狀態(tài)
int ws = node.waitStatus;
//當(dāng)前狀態(tài) < 0 則設(shè)置為 0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)
Node s = node.next;
//后繼節(jié)點(diǎn)為null或者其狀態(tài) > 0 (超時(shí)或者被中斷了)
if (s == null || s.waitStatus > 0) {
s = null;
//從tail節(jié)點(diǎn)來找可用節(jié)點(diǎn)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//喚醒后繼節(jié)點(diǎn)
if (s != null)
LockSupport.unpark(s.thread);
}