多線程之AbstractQueuedSynchronizer(AQS)

AQS 是 java.util.concurrent.locks 包下一個抽象的隊列式同步器類 ;

一、簡介:

爲JUC(java.util.concurrent)包下的很多鎖和同步器提供了基礎(通俗的講把 JUC下依賴與AQS 的同步器和鎖比作車,AQS 就是車的動力系統,具體有哪些車的動力系統依賴AQS呢 ?別急慢慢往下看);

先簡單看完 javadoc 文檔:

大概能定位到幾個重點:

1.子類必須定義改變此狀態的受保護的方法: 子類必須定義改變次狀態的受保護方法,aqs 內部定義了單個原子int值 (state)來表示狀態,int狀態的改變在子類必須受保護,否則會破壞AQS中的同步機制,這是硬性要求'必須'

2. 根據該對象被獲取或者釋放來定義該狀態的含義;舉個例子 :

            ReentrantLock 用 state 來表示鎖的重入次數,每次 lock(); state + 1。unlock(); state - 1;

            CountDownLatch 用 state 表示總數,每次 countDown();  state值 -1;

3. 獨佔模式 共享模式:

獨佔模式:多條線程同一時間操作,只能允許一條線程成功;方法有(acquire 獨佔獲取、release 獨佔發佈、tryAcquire 獨佔嘗試獲取)ReentrantReadWriteLock的寫採用獨佔模式;

共享模式:多條線程同一時間操作,當一條成功時接下來的全部成功; 方法有(acquireShared 共享獲取、releaseShared 共享發佈、 tryAcquireShared 共享嘗試獲取)ReentrantReadWriteLock的讀採用共享模式;

二、實現:

首先AQS內部維護着一個用來記錄等待的鏈表,鏈表如下:

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;
    //表示線程正在等待某個條件
    static final int CONDITION = -2;
    //表示下一個共享模式的節點應該無條件的傳播下去
    static final int PROPAGATE = -3;

    //狀態位 ,分別可以使CANCELLED、SINGNAL、CONDITION、PROPAGATE、0 
    volatile int waitStatus;

    volatile Node prev;//前驅節點
    volatile Node next;//後繼節點
    volatile Thread thread;//等待鎖的線程

    //ConditionObject鏈表的後繼節點或者代表共享模式的節點。
    //因爲Condition隊列只能在獨佔模式下被能被訪問,我們只需要簡單的使用鏈表隊列來鏈接正在等待條件的節點。
    //然後它們會被轉移到同步隊列(AQS隊列)再次重新獲取。
    //由於條件隊列只能在獨佔模式下使用,所以我們要表示共享模式的節點的話只要使用特殊值SHARED來標明即可。
    Node nextWaiter;
    //Returns true if node is waiting in shared mode
    final boolean isShared() {
            return nextWaiter == SHARED;
    }
    .......
 }

AQS中維護的變量 

// transient 關鍵字不參與序列化
// volatile 關鍵字可見的 能保證被修飾元素的可見性
// head 鏈表的頭部(第一個)元素
private transient volatile Node head;
//tail 鏈表的尾部 (最後一個)元素
private transient volatile Node tail;
// 上面講過 用來表示狀態
private volatile int state;

    AQS 中 Node 結點的操作都是基於 CAS (compare and swap) 來實現, 所以可以保證原子性;

AQS中維護的一個內部類 ConditionObject 主要實現 Condition ,若不瞭解大家可以自行查閱資源(Condition.await() 類似於 Object.wait();  Condition.signal()類似於 Object.notify() ; Condition.signalAll()類似於 Object.notifyAll())

public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        private transient Node firstWaiter;    //隊列頭的等待者
        private transient Node lastWaiter;    //隊列尾的等待者
        public ConditionObject() { }
        //將當前線程添加條件等待,放在等待隊列的尾部
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)    //空隊列
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

...

}

 

以 acquire 以及 release 爲例 簡單闡述代碼:

這是 acquire 方法

//該方法是 final 修飾的不可重寫的方法 提供一個模板方法:子類只需重寫 tryAcquire 方法即可
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        //首先 addWaiter 爲當前線程和給定模式創建並對節點進行排隊(這是獨佔模式)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // acquireQueued 自旋阻塞
        selfInterrupt(); // 停止線程 實現就是 Thread.currentThread().interrupt();
}
//嘗試獲取一下資源 需要子類重寫 
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
//爲當前線程和給定模式創建並對節點進行排隊
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);  // new 一個 Node
    Node pred = tail;  // 獲取尾部結點
    if (pred != null) {
        node.prev = pred;   //不爲 null 則把尾部結點設置成新結點的上一個結點 言外之意就是追加一個新的結點 
        if (compareAndSetTail(pred, node)) {   //cas 一次 是否有其他線程更新 tail 
            pred.next = node;
            return node;   //成功就直接返回 不成功 繼續 enq 方法 
        }
    }
    enq(node);   //將節點插入隊列,必要時進行初始化 
    return node;
}
//將節點插入隊列,必要時進行初始化
private Node enq(final Node node) {
    for (;;) {  //自旋 
        Node t = tail;
        if (t == null) {   // 尾部爲null 證明 隊列爲空 則進行初始化 
            // cas 
            if (compareAndSetHead(new Node()))
                tail = head;   //將 head(new Node()) 賦值給 tail 
        } else {
            node.prev = t;   // 與 addWaiter 中的方式相同 cas 設置 tail 
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
//自旋阻塞
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor(); // 獲取 node 的 prev 結點(上一個結點)
            if (p == head && tryAcquire(arg)) { // 若上一個爲 當前的頭部結點 則 tryAcquire 獲取
                setHead(node); // 把當前結點設置爲 頭部結點
                p.next = null; // help GC 
                failed = false; //設置 fail 爲 false 表示 已處理 node 無須 cancelAcquire
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&  // 檢查和更新未能獲取的節點的狀態
                parkAndCheckInterrupt()) //鎖定對象 this
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node); // 取消結點
    }
}
//檢查和更新未能獲取的節點的狀態
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus; 
    if (ws == Node.SIGNAL) // 若上一個結點狀態爲 SIGNAL (-1) 則返回 true
        return true;
    if (ws > 0) { //若大於 0 則表示 已取消 則跳過上一個結點
        do {
            node.prev = pred = pred.prev; 
        } while (pred.waitStatus > 0);
        pred.next = node; 
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //更新狀態 爲 -1 ; new Node 狀態爲 0
    }
    return false;
}
//鎖定對象 並且檢查線程是否存活
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

這是 releas  方法  和 acquire 有異曲同工之妙:

public final boolean release(int arg) {
    if (tryRelease(arg)) { //嘗試發佈 和 tryAcquire 相似 需要子類去實現
        Node h = head; 
        if (h != null && h.waitStatus != 0)  // 判斷頭部結點 和 頭部結點的狀態不爲0(爲 0 則表示未進入阻塞狀態)
            unparkSuccessor(h); // 解鎖 this
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus; // 獲取 head 結點的狀態
    if (ws < 0) 若小於 0
        compareAndSetWaitStatus(node, ws, 0); //cas 爲 0  
    Node s = node.next; // 獲取下一個結點
    if (s == null || s.waitStatus > 0) { //下一個結點爲 null 或者已經取消
        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); //解鎖
}

 

三、所有提供方法: 

boolean  release(int arg) 以專屬模式發佈。
boolean  releaseShared(int arg) 以共享模式發佈。
protected void  setState(int newState) 設置同步狀態的值。
protected boolean  isHeldExclusively() 返回 true 如果同步僅針對當前(調用)線程進行保存
boolean    isQueued(Thread thread) 如果給定的線程當前排隊,則返回true。
protected boolean  tryAcquire(int arg) 嘗試以獨佔模式獲取。
boolean  tryAcquireNanos(int arg, long   nanosTimeout) 嘗試以獨佔模式獲取,如果中斷則中止,如果給定的超時時間失敗
protected int  tryAcquireShared(int arg) 嘗試以共享模式獲取。
boolean  tryAcquireSharedNanos(int arg, long   nanosTimeout) 嘗試以共享模式獲取,如果中斷則中止,如果給定的時間超過,則失敗。
protected boolean  tryRelease(int arg)

嘗試設置狀態以獨佔模式反映版本。

protected boolean

 tryReleaseShared 嘗試將狀態設置爲以共享模式反映發佈。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章