【死磕Java并發(fā)】-----J.U.C之AQS:CLH同步隊列

此篇博客所有源碼均來自JDK 1.8

在上篇博客【死磕Java并發(fā)】-----J.U.C之AQS:AQS簡介中提到了AQS內(nèi)部維護著一個FIFO隊列赋秀,該隊列就是CLH同步隊列寒跳。

CLH同步隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態(tài)的管理啤它,當前線程如果獲取同步狀態(tài)失敗時断箫,AQS則會將當前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個節(jié)點(Node)并將其加入到CLH同步隊列网棍,同時會阻塞當前線程兼蜈,當同步狀態(tài)釋放時,會把首節(jié)點喚醒(公平鎖)序宦,使其再次嘗試獲取同步狀態(tài)睁壁。

在CLH同步隊列中,一個節(jié)點表示一個線程互捌,它保存著線程的引用(thread)潘明、狀態(tài)(waitStatus)、前驅(qū)節(jié)點(prev)秕噪、后繼節(jié)點(next)钳降,其定義如下:

static final class Node {
    /** 共享 */
    static final Node SHARED = new Node();

    /** 獨占 */
    static final Node EXCLUSIVE = null;

    /**
     * 因為超時或者中斷,節(jié)點會被設(shè)置為取消狀態(tài)腌巾,被取消的節(jié)點時不會參與到競爭中的遂填,他會一直保持取消狀態(tài)不會轉(zhuǎn)變?yōu)槠渌麪顟B(tài)铲觉;
     */
    static final int CANCELLED =  1;

    /**
     * 后繼節(jié)點的線程處于等待狀態(tài),而當前節(jié)點的線程如果釋放了同步狀態(tài)或者被取消吓坚,將會通知后繼節(jié)點撵幽,使后繼節(jié)點的線程得以運行
     */
    static final int SIGNAL    = -1;

    /**
     * 節(jié)點在等待隊列中,節(jié)點線程等待在Condition上礁击,當其他線程對Condition調(diào)用了signal()后盐杂,改節(jié)點將會從等待隊列中轉(zhuǎn)移到同步隊列中,加入到同步狀態(tài)的獲取中
     */
    static final int CONDITION = -2;

    /**
     * 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去
     */
    static final int PROPAGATE = -3;

    /** 等待狀態(tài) */
    volatile int waitStatus;

    /** 前驅(qū)節(jié)點 */
    volatile Node prev;

    /** 后繼節(jié)點 */
    volatile Node next;

    /** 獲取同步狀態(tài)的線程 */
    volatile Thread thread;

    Node nextWaiter;

    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {
    }

    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

CLH同步隊列結(jié)構(gòu)圖如下:

CLH同步隊列.png

入列

學(xué)了數(shù)據(jù)結(jié)構(gòu)的我們哆窿,CLH隊列入列是再簡單不過了链烈,無非就是tail指向新節(jié)點、新節(jié)點的prev指向當前最后的節(jié)點挚躯,當前最后一個節(jié)點的next指向當前節(jié)點测垛。代碼我們可以看看addWaiter(Node node)方法:

    private Node addWaiter(Node mode) {
        //新建Node
        Node node = new Node(Thread.currentThread(), mode);
        //快速嘗試添加尾節(jié)點
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //CAS設(shè)置尾節(jié)點
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //多次嘗試
        enq(node);
        return node;
    }

addWaiter(Node node)先通過快速嘗試設(shè)置尾節(jié)點,如果失敗秧均,則調(diào)用enq(Node node)方法設(shè)置尾節(jié)點

    private Node enq(final Node node) {
        //多次嘗試,直到成功為止
        for (;;) {
            Node t = tail;
            //tail不存在号涯,設(shè)置為首節(jié)點
            if (t == null) {
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //設(shè)置為尾節(jié)點
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

在上面代碼中目胡,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設(shè)置尾節(jié)點,該方法可以確保節(jié)點是線程安全添加的链快。在enq(Node node)方法中誉己,AQS通過“死循環(huán)”的方式來保證節(jié)點可以正確添加,只有成功添加后域蜗,當前線程才會從該方法返回巨双,否則會一直執(zhí)行下去。

過程圖如下:

入列.png

出列

CLH同步隊列遵循FIFO霉祸,首節(jié)點的線程釋放同步狀態(tài)后筑累,將會喚醒它的后繼節(jié)點(next),而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設(shè)置為首節(jié)點丝蹭,這個過程非常簡單慢宗,head執(zhí)行該節(jié)點并斷開原首節(jié)點的next和當前節(jié)點的prev即可,注意在這個過程是不需要使用CAS來保證的奔穿,因為只有一個線程能夠成功獲取到同步狀態(tài)镜沽。過程圖如下:

出列.png

參考資料

Doug Lea:《Java并發(fā)編程實戰(zhàn)》
方騰飛:《Java并發(fā)編程的藝術(shù)》


個人微信公眾號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贱田,隨后出現(xiàn)的幾起案子缅茉,更是在濱河造成了極大的恐慌,老刑警劉巖男摧,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔬墩,死亡現(xiàn)場離奇詭異译打,居然都是意外死亡,警方通過查閱死者的電腦和手機筹我,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門扶平,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蔬蕊,你說我怎么就攤上這事结澄。” “怎么了岸夯?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵麻献,是天一觀的道長。 經(jīng)常有香客問我猜扮,道長勉吻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任旅赢,我火速辦了婚禮齿桃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煮盼。我一直安慰自己短纵,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布僵控。 她就那樣靜靜地躺著香到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪报破。 梳的紋絲不亂的頭發(fā)上悠就,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音充易,去河邊找鬼梗脾。 笑死,一個胖子當著我的面吹牛盹靴,可吹牛的內(nèi)容都是我干的藐唠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼鹉究,長吁一口氣:“原來是場噩夢啊……” “哼宇立!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起自赔,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妈嘹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绍妨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润脸,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡柬脸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毙驯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒堕。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爆价,靈堂內(nèi)的尸體忽然破棺而出垦巴,到底是詐尸還是另有隱情,我是刑警寧澤铭段,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布骤宣,位于F島的核電站,受9級特大地震影響序愚,放射性物質(zhì)發(fā)生泄漏憔披。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一爸吮、第九天 我趴在偏房一處隱蔽的房頂上張望芬膝。 院中可真熱鬧,春花似錦形娇、人聲如沸蔗候。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纫事,卻和暖如春勘畔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丽惶。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工炫七, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钾唬。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓万哪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抡秆。 傳聞我的和親對象是個殘疾皇子奕巍,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

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