AbstractQueuedSynchronizer源碼--獨占式

獨占模式

1.獲取獨占鎖

1.1 acquire()

/**
 * 獲取獨占鎖渠概,對中斷不敏感劣光。
 * 首先嘗試獲取一次鎖航揉,如果成功熟呛,則返回临扮;
 * 否則會把當前線程包裝成Node插入到隊列中诫惭,在隊列中會檢測是否為head的直接后繼窃祝,并嘗試獲取鎖,
 * 如果獲取失敗,則會通過LockSupport阻塞當前線程众旗,直至被釋放鎖的線程喚醒或者被中斷竟贯,隨后再次嘗試獲取鎖,如此反復逝钥。
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

1.2 addWaiter()

/**
 * 在隊列中新增一個節(jié)點。
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 快速嘗試
    if (pred != null) {
        node.prev = pred;
        // 通過CAS在隊尾插入當前節(jié)點
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 初始情況或者在快速嘗試失敗后插入節(jié)點
    enq(node);
    return node;
}

/**
 * 通過循環(huán)+CAS在隊列中成功插入一個節(jié)點后返回拱镐。
 */
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 初始化head和tail
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            /*
             * AQS的精妙就是體現(xiàn)在很多細節(jié)的代碼艘款,比如需要用CAS往隊尾里增加一個元素
             * 此處的else分支是先在CAS的if前設(shè)置node.prev = t,而不是在CAS成功之后再設(shè)置沃琅。
             * 一方面是基于CAS的雙向鏈表插入目前沒有完美的解決方案哗咆,另一方面這樣子做的好處是:
             * 保證每時每刻tail.prev都不會是一個null值,否則如果node.prev = t
             * 放在下面if的里面益眉,會導致一個瞬間tail.prev = null晌柬,這樣會使得隊列不完整。
             */
            node.prev = t;
            // CAS設(shè)置tail為node郭脂,成功后把老的tail也就是t連接到node年碘。
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

1.3 acquireQueued()

/**
 * 在隊列中的節(jié)點通過此方法獲取鎖,對中斷不敏感展鸡。
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            /*
             * 檢測當前節(jié)點前驅(qū)是否head屿衅,這是試獲取鎖的資格。
             * 如果是的話莹弊,則調(diào)用tryAcquire嘗試獲取鎖,
             * 成功涤久,則將head置為當前節(jié)點。
             */
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            /*
             * 如果未成功獲取鎖則根據(jù)前驅(qū)節(jié)點判斷是否要阻塞忍弛。
             * 如果阻塞過程中被中斷响迂,則置interrupted標志位為true。
             * shouldParkAfterFailedAcquire方法在前驅(qū)狀態(tài)不為SIGNAL的情況下都會循環(huán)重試獲取鎖细疚。
             */
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

1.4

/**
 * 喚醒后繼線程蔗彤。
 */
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    // 嘗試將node的等待狀態(tài)置為0,這樣的話,后繼爭用線程可以有機會再嘗試獲取一次鎖。
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    /*
     * 這里的邏輯就是如果node.next存在并且狀態(tài)不為取消,則直接喚醒s即可
     * 否則需要從tail開始向前找到node之后最近的非取消節(jié)點幕与。
     *
     * 這里為什么要從tail開始向前查找也是值得琢磨的:
     * 如果讀到s == null挑势,不代表node就為tail,參考addWaiter以及enq函數(shù)中的我的注釋啦鸣。
     * 不妨考慮到如下場景:
     * 1. node某時刻為tail
     * 2. 有新線程通過addWaiter中的if分支或者enq方法添加自己
     * 3. compareAndSetTail成功
     * 4. 此時這里的Node s = node.next讀出來s == null潮饱,但事實上node已經(jīng)不是tail,它有后繼了!
     */
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

2.釋放獨占鎖

釋放獨占鎖诫给,首先會調(diào)用tryRelease,在完全釋放掉獨占鎖后香拉,這時后繼線程是可以取到獨占鎖的。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        /*
         * 此時的head節(jié)點可能有3種情況:
         * 1. null (AQS的head延遲初始化+無競爭的情況)
         * 2. 當前線程在獲取鎖時new出來的節(jié)點通過setHead設(shè)置的
         * 3. 由于通過tryRelease已經(jīng)完全釋放掉了獨占鎖中狂,有新的節(jié)點在acquireQueued中獲取到了獨占鎖凫碌,并設(shè)置了head

         * 第三種情況可以再分為兩種情況:
         * (一)時刻1:線程A通過acquireQueued,持鎖成功胃榕,set了head
         *          時刻2:線程B通過tryAcquire試圖獲取獨占鎖失敗失敗盛险,進入acquiredQueued
         *          時刻3:線程A通過tryRelease釋放了獨占鎖
         *          時刻4:線程B通過acquireQueued中的tryAcquire獲取到了獨占鎖并調(diào)用setHead
         *          時刻5:線程A讀到了此時的head實際上是線程B對應的node
         * (二)時刻1:線程A通過tryAcquire直接持鎖成功,head為null
         *          時刻2:線程B通過tryAcquire試圖獲取獨占鎖失敗失敗勋又,入隊過程中初始化了head苦掘,進入acquiredQueued
         *          時刻3:線程A通過tryRelease釋放了獨占鎖,此時線程B還未開始tryAcquire
         *          時刻4:線程A讀到了此時的head實際上是線程B初始化出來的傀儡head
         */
        Node h = head;
        // head節(jié)點狀態(tài)不會是CANCELLED楔壤,所以這里h.waitStatus != 0相當于h.waitStatus < 0
        if (h != null && h.waitStatus != 0)
            // 喚醒后繼線程鹤啡,此函數(shù)在acquire中已經(jīng)分析過,不再列舉說明
            unparkSuccessor(h);
        return true;
    }
    return false;
}

總結(jié)一下上面的邏輯:
1.調(diào)用tryRelease
2.如果tryRelease返回true也就是獨占鎖被完全釋放蹲嚣,喚醒后繼線程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末递瑰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隙畜,更是在濱河造成了極大的恐慌抖部,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件议惰,死亡現(xiàn)場離奇詭異您朽,居然都是意外死亡,警方通過查閱死者的電腦和手機换淆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門哗总,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倍试,你說我怎么就攤上這事讯屈。” “怎么了县习?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵涮母,是天一觀的道長谆趾。 經(jīng)常有香客問我,道長叛本,這世上最難降的妖魔是什么沪蓬? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮来候,結(jié)果婚禮上跷叉,老公的妹妹穿的比我還像新娘。我一直安慰自己营搅,他們只是感情好云挟,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著转质,像睡著了一般园欣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上休蟹,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天沸枯,我揣著相機與錄音,去河邊找鬼赂弓。 笑死辉饱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的拣展。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼缔逛,長吁一口氣:“原來是場噩夢啊……” “哼备埃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起褐奴,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤按脚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后敦冬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辅搬,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年脖旱,在試婚紗的時候發(fā)現(xiàn)自己被綠了堪遂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡萌庆,死狀恐怖溶褪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情践险,我是刑警寧澤猿妈,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布吹菱,位于F島的核電站,受9級特大地震影響彭则,放射性物質(zhì)發(fā)生泄漏鳍刷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一俯抖、第九天 我趴在偏房一處隱蔽的房頂上張望输瓜。 院中可真熱鬧,春花似錦蚌成、人聲如沸前痘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芹缔。三九已至,卻和暖如春瓶盛,著一層夾襖步出監(jiān)牢的瞬間最欠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工惩猫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芝硬,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓轧房,卻偏偏與公主長得像拌阴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奶镶,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

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