AQS學(xué)習(xí)

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ì)有兩種被打斷的情況

  1. 前節(jié)點(diǎn)釋放執(zhí)行權(quán)利宋彼,喚醒當(dāng)前節(jié)點(diǎn)弄砍。
  2. 當(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);
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末紧显,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缕棵,更是在濱河造成了極大的恐慌孵班,老刑警劉巖涉兽,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異篙程,居然都是意外死亡枷畏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門虱饿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拥诡,“玉大人,你說我怎么就攤上這事氮发】嗜猓” “怎么了?”我有些...
    開封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵爽冕,是天一觀的道長(zhǎng)仇祭。 經(jīng)常有香客問我,道長(zhǎng)颈畸,這世上最難降的妖魔是什么乌奇? 我笑而不...
    開封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮眯娱,結(jié)果婚禮上礁苗,老公的妹妹穿的比我還像新娘。我一直安慰自己徙缴,他們只是感情好试伙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著娜搂,像睡著了一般迁霎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上百宇,一...
    開封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天考廉,我揣著相機(jī)與錄音,去河邊找鬼携御。 笑死昌粤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啄刹。 我是一名探鬼主播涮坐,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼誓军!你這毒婦竟也來了袱讹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捷雕,沒想到半個(gè)月后椒丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡救巷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年壶熏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浦译。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棒假,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出精盅,到底是詐尸還是另有隱情帽哑,我是刑警寧澤,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布渤弛,位于F島的核電站祝拯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏她肯。R本人自食惡果不足惜佳头,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晴氨。 院中可真熱鬧康嘉,春花似錦、人聲如沸籽前。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枝哄。三九已至肄梨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挠锥,已是汗流浹背众羡。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蓖租,地道東北人粱侣。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蓖宦,于是被迫代替她去往敵國(guó)和親齐婴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容