J.U.C|同步隊列(CLH)

1、寫在前面


在上篇咱們聊到AQS的原理,具體參見《J.U.C|AQS原理》node

這篇咱們來給你們聊聊AQS中核心同步隊列(CLH)。segmentfault

2、什麼是同步隊列(CLH)


同步隊列安全

一個FIFO雙向隊列,隊列中每一個節點等待前驅節點釋放共享狀態(鎖)被喚醒就能夠了。spa

AQS如何使用它?線程

AQS依賴它來完成同步狀態的管理,當前線程若是獲取同步狀態失敗時,AQS則會將當前線程已經等待狀態等信息構形成一個節點(Node)並將其加入到CLH同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。code

Node節點面貌?隊列

static final class Node {
        // 節點分爲兩種模式: 共享式和獨佔式
        /** 共享式 */
        static final Node SHARED = new Node();
        /** 獨佔式 */
        static final Node EXCLUSIVE = null;

        /** 等待線程超時或者被中斷、須要從同步隊列中取消等待(也就是放棄資源的競爭),此狀態不會在改變 */
        static final int CANCELLED =  1;
        /** 後繼節點會處於等待狀態,當前節點線程若是釋放同步狀態或者被取消則會通知後繼節點線程,使後繼節點線程的得以運行 */
        static final int SIGNAL    = -1;
        /** 節點在等待隊列中,線程在等待在Condition 上,其餘線程對Condition調用singnal()方法後,該節點加入到同步隊列中。 */
        static final int CONDITION = -2;
        /**
         * 表示下一次共享式獲取同步狀態的時會被無條件的傳播下去。
         */
        static final int PROPAGATE = -3;

        /**等待狀態*/
        volatile int waitStatus;

        /**前驅節點 */
        volatile Node prev;

        /**後繼節點*/
        volatile Node next;

        /**獲取同步狀態的線程 */
        volatile Thread thread;

        /**連接下一個等待狀態 */
        Node nextWaiter;
        
        // 下面一些方法就不貼了
    }

CLH同步隊列的結構圖
圖片描述
這裏是基於CAS(保證線程的安全)來設置尾節點的。圖片

3、入列操做


如上圖瞭解了同步隊列的結構, 咱們在分析其入列操做在簡單不過。無非就是將tail(使用CAS保證原子操做)指向新節點,新節點的prev指向隊列中最後一節點(舊的tail節點),原隊列中最後一節點的next節點指向新節點以此來創建聯繫,來張圖幫助你們理解。
圖片描述資源

源碼
源碼咱們能夠經過AQS中的如下兩個方法來了解下
addWaiter方法get

private Node addWaiter(Node mode) {
// 以給定的模式來構建節點, mode有兩種模式 
//  共享式SHARED, 獨佔式EXCLUSIVE;
  Node node = new Node(Thread.currentThread(), mode);
    // 嘗試快速將該節點加入到隊列的尾部
    Node pred = tail;
     if (pred != null) {
        node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 若是快速加入失敗,則經過 anq方式入列
        enq(node);
        return node;
    }

先經過addWaiter(Node node)方法嘗試快速將該節點設置尾成尾節點,設置失敗走enq(final Node node)方法

enq

private Node enq(final Node node) {
// CAS自旋,直到加入隊尾成功        
for (;;) {
    Node t = tail;
        if (t == null) { // 若是隊列爲空,則必須先初始化CLH隊列,新建一個空節點標識做爲Hader節點,並將tail 指向它
            if (compareAndSetHead(new Node()))
                tail = head;
            } else {// 正常流程,加入隊列尾部
                node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                }
            }
        }
    }

經過「自旋」也就是死循環的方式來保證該節點能順利的加入到隊列尾部,只有加入成功纔會退出循環,不然會一直循序直到成功。

上述兩個方法都是經過compareAndSetHead(new Node())方法來設置尾節點,以保證節點的添加的原子性(保證節點的添加的線程安全。)

4、出列操做


同步隊列(CLH)遵循FIFO,首節點是獲取同步狀態的節點,首節點的線程釋放同步狀態後,將會喚醒它的後繼節點(next),然後繼節點將會在獲取同步狀態成功時將本身設置爲首節點,這個過程很是簡單。以下圖
圖片描述

設置首節點是經過獲取同步狀態成功的線程來完成的(獲取同步狀態是經過CAS來完成),只能有一個線程可以獲取到同步狀態,所以設置頭節點的操做並不須要CAS來保證,只須要將首節點設置爲其原首節點的後繼節點並斷開原首節點的next(等待GC回收)應用便可。

5、總結


聊完後咱們來總一下,同步隊列就是一個FIFO雙向對隊列,其每一個節點包含獲取同步狀態失敗的線程應用、等待狀態、前驅節點、後繼節點、節點的屬性類型以及名稱描述。

其入列操做也就是利用CAS(保證線程安全)來設置尾節點,出列就很簡單了直接將head指向新頭節點並斷開老頭節點聯繫就能夠了。

相關文章
相關標籤/搜索