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

此篇博客所有源碼均來(lái)自JDK 1.8

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

CLH同步隊(duì)列是一個(gè)FIFO雙向隊(duì)列,AQS依賴(lài)它來(lái)完成同步狀態(tài)的管理,當(dāng)前線程如果獲取同步狀態(tài)失敗時(shí),AQS則會(huì)將當(dāng)前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個(gè)節(jié)點(diǎn)(Node)并將其加入到CLH同步隊(duì)列,同時(shí)會(huì)阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時(shí),會(huì)把首節(jié)點(diǎn)喚醒(公平鎖),使其再次嘗試獲取同步狀態(tài)。

在CLH同步隊(duì)列中,一個(gè)節(jié)點(diǎn)表示一個(gè)線程,它保存著線程的引用(thread)、狀態(tài)(waitStatus)、前驅(qū)節(jié)點(diǎn)(prev)、后繼節(jié)點(diǎn)(next),其定義如下:

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

    /** 獨(dú)占 */
    static final Node EXCLUSIVE = null;

    /**
     * 因?yàn)槌瑫r(shí)或者中斷,節(jié)點(diǎn)會(huì)被設(shè)置為取消狀態(tài),被取消的節(jié)點(diǎn)時(shí)不會(huì)參與到競(jìng)爭(zhēng)中的,他會(huì)一直保持取消狀態(tài)不會(huì)轉(zhuǎn)變?yōu)槠渌麪顟B(tài);
     */
    static final int CANCELLED =  1;

    /**
     * 后繼節(jié)點(diǎn)的線程處于等待狀態(tài),而當(dāng)前節(jié)點(diǎn)的線程如果釋放了同步狀態(tài)或者被取消,將會(huì)通知后繼節(jié)點(diǎn),使后繼節(jié)點(diǎn)的線程得以運(yùn)行
     */
    static final int SIGNAL    = -1;

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

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

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

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

    /** 后繼節(jié)點(diǎn) */
    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同步隊(duì)列結(jié)構(gòu)圖如下:

CLH同步隊(duì)列.png

入列

學(xué)了數(shù)據(jù)結(jié)構(gòu)的我們,CLH隊(duì)列入列是再簡(jiǎn)單不過(guò)了,無(wú)非就是tail指向新節(jié)點(diǎn)、新節(jié)點(diǎn)的prev指向當(dāng)前最后的節(jié)點(diǎn),當(dāng)前最后一個(gè)節(jié)點(diǎn)的next指向當(dāng)前節(jié)點(diǎn)。代碼我們可以看看addWaiter(Node node)方法:

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

addWaiter(Node node)先通過(guò)快速?lài)L試設(shè)置尾節(jié)點(diǎn),如果失敗,則調(diào)用enq(Node node)方法設(shè)置尾節(jié)點(diǎn)

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

在上面代碼中,兩個(gè)方法都是通過(guò)一個(gè)CAS方法compareAndSetTail(Node expect, Node update)來(lái)設(shè)置尾節(jié)點(diǎn),該方法可以確保節(jié)點(diǎn)是線程安全添加的。在enq(Node node)方法中,AQS通過(guò)“死循環(huán)”的方式來(lái)保證節(jié)點(diǎn)可以正確添加,只有成功添加后,當(dāng)前線程才會(huì)從該方法返回,否則會(huì)一直執(zhí)行下去。

過(guò)程圖如下:

入列.png

出列

CLH同步隊(duì)列遵循FIFO,首節(jié)點(diǎn)的線程釋放同步狀態(tài)后,將會(huì)喚醒它的后繼節(jié)點(diǎn)(next),而后繼節(jié)點(diǎn)將會(huì)在獲取同步狀態(tài)成功時(shí)將自己設(shè)置為首節(jié)點(diǎn),這個(gè)過(guò)程非常簡(jiǎn)單,head執(zhí)行該節(jié)點(diǎn)并斷開(kāi)原首節(jié)點(diǎn)的next和當(dāng)前節(jié)點(diǎn)的prev即可,注意在這個(gè)過(guò)程是不需要使用CAS來(lái)保證的,因?yàn)橹挥幸粋€(gè)線程能夠成功獲取到同步狀態(tài)。過(guò)程圖如下:

出列.png

參考資料

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


個(gè)人微信公眾號(hào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容