AQS數據結構及常用方法源碼解析

1 同步隊列(CLH)

1.1 簡介

CLH隊列是Craig, Landin, and Hagersten三人發明的一種基於雙向鏈表數據結構的隊列。是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程僅僅在本地變量上自旋,它不斷輪詢前驅的狀態,假設發現前驅釋放了鎖就結束自旋。
JAVA中的CLH隊列是原CLH隊列的一個變種,線程由原來的自旋機制改爲阻塞機制。
在這裏插入圖片描述

1.2 Node結構

static final class Node {
        
        /**################節點模式##################*/
        /** 共享*/
        static final Node SHARED = new Node();
        /** 獨佔 */
        static final Node EXCLUSIVE = null;
        /**
         *  CANCELLED:等待線程超時或者被中斷、需要從同步隊列中取消等待(也就是放棄資源的競爭)
         *  SIGNAL:後繼節點會處於等待狀態,當前節點線程如果釋放同步狀態或者被取消則會通知後繼節點線程,使後繼節點線程的得以運行
         *  CONDITION:節點在等待隊列中,線程在等待在Condition 上,其他線程對Condition調用singnal()方法後,該節點加入到同步隊列中。
         *  PROPAGATE:表示下一次共享式獲取同步狀態的時會被無條件的傳播下去。
         */
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        /**等待狀態*/
        volatile int waitStatus;
        /**前指針*/
        volatile Node prev;
        /**後指針*/
        volatile Node next;
        /**線程*/
        volatile Thread thread;

        /**
         * AQS中條件隊列是使用單向列表保存的,用nextWaiter來連接。
         */
        Node nextWaiter;
        /** 判斷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) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

2 state 同步狀態

state 的作用,它爲 0 的時候代表沒有線程佔有鎖,可以去爭搶這個鎖,用 CAS 將 state 設爲 1,如果 CAS 成功,說明搶到了鎖,這樣其他線程就搶不到了,如果鎖重入的話,state進行 +1 就可以,解鎖就是減 1,直到 state 又變爲 0,代表釋放鎖,所以 lock() 和 unlock() 必須要配對啊。然後喚醒等待隊列中的第一個線程,讓其來佔有鎖。

/**
 * 得到同步狀態
 */
protected final int getState() {
    return state;
}

/**
 * 設置同步狀態
 */
protected final void setState(int newState) {
    state = newState;
}

/**
 * 利用unsafe的cas設置同步狀態(原子操作)
 */
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

3 資源

AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源state的獲取與釋放方式即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現以下幾種方法:

(1) tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
(2) tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
(3) tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
(4) tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。

4 addWaiter

該方法用於將當前線程根據不同的模式加入到等待隊列的隊尾,並返回當前線程所在的結點。

4.1 addWaiter

private Node addWaiter(Node mode) {
    //1 創建一個節點
    Node node = new Node(Thread.currentThread(), mode);
    //2 獲取隊尾,隊尾不爲空
    Node pred = tail;
    if (pred != null) {
        //2.1 設置新節點的前驅是隊尾
        node.prev = pred;
        //2.2 cas快速設置入隊尾(expect=pred,update=tail)
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //3 自旋設置隊尾
    enq(node);
    
    return node;
}

4.2 enq

private Node enq(final Node node) {
    //自旋
    for (;;) {
        //獲取隊尾
        Node t = tail;
        //如果隊尾爲空
        if (t == null) {
            //創建隊頭、隊尾
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //隊尾的前驅節點是隊尾
            node.prev = t;
            //cas 設置隊尾(expect=tail,update=node)
            if (compareAndSetTail(t, node)) {
                //把原來隊尾的後驅節點設置爲更新後的隊尾節點
                t.next = node;
                return t;
            }
        }
    }
}

5 acquire

/**
 * 以獨佔、忽略打斷的方式獲取資源
 */
public final void acquire(int arg) {
    // 嘗試獲取資源,入隊尾,獲取資源或進入等待
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

5.1 acquireQueued

/*
 * 獲取資源或進入等待
 */
final boolean acquireQueued(final Node node, int arg) {
    //創建失敗狀態
    boolean failed = true;
    try {
        //打斷狀態
        boolean interrupted = false;
        //自旋
        for (;;) {
            //獲取當前節點的前驅節點
            final Node p = node.predecessor();
            //如果當前節點是隊列第二個節點並且拿到了資源
            if (p == head && tryAcquire(arg)) {
                //設置隊頭爲當前節點    
                setHead(node);
                //把隊頭的後指針設置爲null,help GC
                p.next = null;  
                failed = false;
                return interrupted;
            }            
            //判斷是否進入等待
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果沒有搶到資源,取消當前節點獲取資源
        if (failed)
            cancelAcquire(node);
    }
}

5.2 shouldParkAfterFailedAcquire

/**
 * 是否能夠進入等待狀態
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //pred 節點的狀態         
    int ws = pred.waitStatus;
    
    if (ws == Node.SIGNAL)
        /*
         * SIGNAL狀態,當前節點直接進入等待狀態,返回結果爲true
         */
        return true;
    if (ws > 0) {
        /*
         * 只有CANCELLED狀態大於0
         * pred爲CANCELLED狀態,及無效狀態,爲當前node找到有效的前驅pred
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        
        //更新前驅節點的後指針
        pred.next = node;
    } else {
        /*
         * cas設置前驅節點爲SIGNAL狀態
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    
    /**
     * 如果前驅節點不爲SIGNAL,返回false,再次進入acquireQueued的自旋
     */
    return false;
}

5.3 parkAndCheckInterrupt

/**
 * 使用LockSupport進入等待,LockSupport調用unsafe方法,unsafe爲java調用C
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

6 release

6.1 release

/**
 * 獨佔式釋放資源
 */
public final boolean release(int arg) {
        
    //釋放資源        
    if (tryRelease(arg)) {
        //獲取隊頭
        Node h = head;
        //隊頭不爲空,隊頭不是初始化,喚醒等待節點
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

6.2 unparkSuccessor

/*
 * 喚醒等待節點
 */
private void unparkSuccessor(Node node) {
    //獲取當前節點狀態
    int ws = node.waitStatus;
    
    //如果狀態小於0,初始化狀態
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    //獲取當前節點的後繼節點
    Node s = node.next;
    
    //後繼節點爲空或後繼節點的狀態爲CANCELLED狀態
    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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章