aqs源碼

aqs

AQS(AbstractQueuedSynchronizer)是JAVA中衆多鎖以及併發工具的基礎,其底層採用樂觀鎖,大量使用了CAS操作, 並且在衝突時,採用自旋方式重試,以實現輕量級和高效地獲取鎖。

AQS雖然被定義爲抽象類,但事實上它並不包含任何抽象方法。AQS是被設計爲支持多種用途,如果定義抽象方法,子類在繼承的時候就需要實現所有抽象方法,所以AQS將需要子類覆蓋的方法都設計爲protect方法,默認拋出UnsupportedOperationException異常,。如果子類用到這些方法就必須重寫,否則會拋出異常,如果沒有用到則不需要做任何操作。

AbstractQueuedSynchronizer只繼承了AbstractOwnableSynchronizer,實現了java.io.Serializable接口。

AbstractOwnableSynchronizer類是一種同步器,這個類僅有set和get獨佔線程資源。

public abstract class AbstractOwnableSynchronizer
     implements java.io.Serializable {
 
     private static final long serialVersionUID = 3737899427754241961L;
 
     protected AbstractOwnableSynchronizer() { }

     private transient Thread exclusiveOwnerThread;

     protected final void setExclusiveOwnerThread(Thread t) {
         exclusiveOwnerThread = t;
     }

     protected final Thread getExclusiveOwnerThread() {
         return exclusiveOwnerThread;
     }
 }

exclusiveOwnerThread 即獨佔資源的線程。

AQS原理

AQS維護了一個state變量和node雙向鏈表。

state是已獲取資源佔有許可的數量。例如線程調用acquire(1)獲取資源的許可,acquire會調用一次tryAcquire(1)獲取資源。如果獲取成功,則state加1並且調用父類的設置獨佔線程將當前線程設置爲獨佔線程。如果獲取失敗,則說明已經有線程佔用了這個資源,需要等待佔用釋放。此時將該線程封裝成node節點,加入雙向鏈表,之後Locksupport.pack()堵塞當前線程。如果這個線程被喚醒則繼續循環調用tryAcquire獲取許可,如果獲取到了將自己的node節點設置爲鏈表的頭結點並把之前的頭結點去掉。如果線程釋放資源,調用release方法,release方法會調用tryRelease方法嘗試釋放資源,如果釋放成功,則state減1,再調用AQS的父類AbstractOwnableSynchronizer的設置獨佔線程爲null,再locksupport.unpack()雙向node鏈表的頭node節點的線程,恢復其執行。

源碼

成員變量

private transient volatile Node head;

private transient volatile Node tail;

private volatile int state;

private static final Unsafe unsafe = Unsafe.getUnsafe();

private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;

static {
    try {
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));

    } catch (Exception ex) { throw new Error(ex); }
}

有一個頭尾節點和state變量,實現CAS的Unsafe的工具類,還有一些偏移量,都用於Unsafe的CAS操作,通過靜態代碼塊進行初始化,通過objectFieldOffset獲取對應字段相對於該對象的起始地址的偏移量。

Node節點

static final class Node {
    //共享模式
    static final Node SHARED = new Node();
    //獨佔模式
    static final Node EXCLUSIVE = null;
    //當線程等待超時或者被中斷,則取消等待
    static final int CANCELLED =  1;
    //後繼節點處於等待狀態,當前節點(爲-1)被取消或者中斷時會通知後繼節點,使後繼節點的線程得以運行
    static final int SIGNAL    = -1;
    //當前節點處於等待隊列,節點線程等待在Condition上,當其他線程對condition執行signall方法時,等待隊列轉移到同步隊列,加入到對同步狀態的獲取。
    static final int CONDITION = -2;
    //與共享模式相關,在共享模式中,該狀態標識結點的線程處於可運行狀態。
    static final int PROPAGATE = -3;
    //狀態
    volatile int waitStatus;
    //上一個節點
    volatile Node prev;
    //下一個節點
    volatile Node next;
    //節點所代表的線程
    volatile Thread thread;
    //Node既可以作爲同步隊列節點使用,也可以作爲Condition的等待隊列節點使用
    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() {    // Used to establish initial head or SHARED marker
    }

    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;
    }
}

enq方法

private Node enq(final Node node) {
   for (;;) {
       Node t = tail;
       if (t == null) { // Must initialize
           if (compareAndSetHead(new Node()))
               tail = head;
       } else {
           node.prev = t;
           if (compareAndSetTail(t, node)) {
               t.next = node;
               return t;
           }
       }
   }
}

enq方法是將node加入鏈表,如果tail尾節點爲空則必須進行初始化,如果tail不爲空,則將node的前指針指向tail,通過CAS將tail的指向改爲node,然後設置t.next爲node,完成node插入鏈表尾部。

addWaiter方法

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

addWaiter方法包裝node節點,放入node雙向鏈表。如果tail不爲空則說明初始化過了直接將node加入鏈表尾部,如果爲空則進行初始化再將node加入鏈表尾部。

共享模式

acquireShared(獲取鎖)
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

嘗試去獲取資源,如果沒有獲取資源返回負數,tryAcquireShared方法需要子類自己去實現,如果不實現會直接拋異常(在讀寫鎖的Sync實現);如果沒有獲取到資源加入等待隊列等待獲取資源。

doAcquireShared
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //找先驅結點
            final Node p = node.predecessor();
            if (p == head) {
                //嘗試獲取資源
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //獲取鎖之後,設置當前節點爲頭節點,去喚醒
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    //如果是因爲中斷醒來則設置中斷標記位
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 獲取鎖失敗
        if (failed)
            cancelAcquire(node);
    }
}

先吧當前節點加入到隊列尾部,然後進入自旋,自旋的目的是爲了獲取資源或者阻塞,如果此節點的前一個node是head節點,就去獲取資源,如果獲取失敗就執行shouldParkAfterFailedAcquire,將前一個node設置爲SIGNAL,獲取成功就setHeadAndPropagate。

setHeadAndPropagate
////兩個入參,一個是當前成功獲取共享鎖的節點,一個就是tryAcquireShared方法的返回值,它可能大於0也可能等於0
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    //設置新的頭節點
    setHead(node);
    //propagate > 0 代表還有資源,還可以繼續喚醒  | h.waitStatus 是 -1 or -3
    if (propagate > 0 || h == null || h.waitStatus < 0) {
        Node s = node.next;
        //如果當前節點的後繼節點是共享類型獲取沒有後繼節點,則進行喚醒
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

會喚醒後面的所有節點

doReleaseShared(喚醒)
private void doReleaseShared() {
    for (;;) {
        //從頭結點開始 head已是上面設置的head節點
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //表示需要喚醒(-1)
            if (ws == Node.SIGNAL) {
                //CAS 將狀態置爲0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //喚醒
                unparkSuccessor(h);
            }
            //如果後繼節點暫時不需要喚醒,則把當前節點狀態設置爲PROPAGATE確保以後可以傳遞下去
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //如果頭結點沒有發生變化,表示設置完成,退出循環
        //如果頭結點發生變化,比如說其他線程獲取到了鎖,爲了使自己的喚醒動作可以傳遞,必須進行重試
        if (h == head)                   // loop if head changed
            break;
    }
}
unparkSuccessor方法
 private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    //next 節點爲空 或者狀態爲取消
    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);
}

用unpark()喚醒等待隊列中最前邊的那個未放棄線程,node的waitStatus爲signal或condition,則可以喚醒,先重置node的waitStatus爲0(允許失敗),找到下一個需要喚醒的節點喚醒。

從後往前找是因爲下一個任務有可能被取消了,節點就有可能爲null

shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

主要是進行的狀態的檢查,如果前一個節點的狀態是-1則返回true;如果前一個節點取消了,那就向前找到一個沒有被取消的節點,將取消的節點捨棄,如果前一個節點沒有被取消則將節點狀態設置爲-1.

releaseShared( 釋放鎖)
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
            doReleaseShared();
        return true;
    }
    return false;
}

獨佔模式

acquire(獲取鎖)
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

首先也是嘗試獲取資源,如果獲取到資源則直接返回了,如果沒有獲取到資源則執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),將該線程加入隊列節點尾部。

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);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

和共享模式類似,先獲取該節點的前一個節點,如果前一個節點是頭結點就嘗試獲取資源。如果獲取到資源則把這個接地點設爲頭節點 直接返回了;如果沒有獲取到資源則進入阻塞掛起。

掛起邏輯同上。

cancelAcquire
private void cancelAcquire(Node node) {
    //如果節點不存在直接返回
    if (node == null)
        return;
    node.thread = null;

    Node pred = node.prev;
    //跳過前面已經取消的前置節點
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    //將node的狀態設置爲1 其他節點在處理時就可以跳過
    node.waitStatus = Node.CANCELLED;
    //如果是尾節點直接刪除返回
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //否則當前節點的前置節點不是頭節點且它後面的節點等待它喚醒
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                //刪除該node
                compareAndSetNext(pred, predNext, next);
        } else {
            //要麼當前節點的前置節點是頭結點,直接喚醒當前節點的後繼節點
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

獲取鎖資源失敗的處理,即自己實現的獲取資源的邏輯出異常的時候會進入到這裏。(共享模式同這裏的)

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;
}

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    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);
}

條件隊列(ConditionObject)

使用場景

//首先創建一個可重入鎖,它本質是獨佔鎖
private final ReentrantLock takeLock = new ReentrantLock();
//創建該鎖上的條件隊列
private final Condition notEmpty = takeLock.newCondition();
//使用過程
public E take() throws InterruptedException {
    //首先進行加鎖
    takeLock.lockInterruptibly();
    try {
        //如果隊列是空的,則進行等待
        notEmpty.await();
        //取元素的操作...
        
        //如果有剩餘,則喚醒等待元素的線程
        notEmpty.signal();
    } finally {
        //釋放鎖
        takeLock.unlock();
    }
    //取完元素以後喚醒等待放入元素的線程
}

Condition一般都是配合一個顯式鎖Lock一起使用,Lock接口的方法中有一個newCondition()方法用於生成Condition對象。
通過ReentrantLock的lock方法,如果獲取不到鎖當前線程會進入AQS隊列阻塞;被喚醒後繼續獲取鎖,如果獲取到鎖,移出AQS隊列,繼續執行;遇到Condition的await方法,加入“條件隊列”,阻塞線程;被其他線程的signal方法喚醒,從“條件隊列”中刪除,並加入到AQS隊列,如果獲取到鎖就繼續執行。可以看到上述操作,線程節點(Node)其實在兩個隊列之間切換,由於“條件隊列”在被喚醒時 都是從頭開始遍歷,所以只需要使用單向鏈表實現即可。

public interface Condition {

    void await() throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

ConditionObject 實現了 Condition接口,Condition接口中一個有7個接口:

  • await : 使用這個鎖必須放在一個顯式鎖的lock和unlock之間,調用該方法後當前線程會釋放鎖並被阻塞,直到其他線程通過調用同一個Condition對象的signal或者signalAll方法或被中斷,再次被喚醒。(可被中斷)
  • awaitUninterruptibly : 此方式是不可被中斷的,只能通過其他線程調用同一個Condition對象的signal或者signalAll方法,才能被喚醒。(不響應中斷)
  • awaitNanos : 等待納秒時間
  • await(long time, TimeUnit unit) : 等待一個指定時間
  • awaitUntil : 等待直到一個截止時間
  • signal : 喚醒等待隊列中的第一個節點
  • signalAll : 喚醒等待隊列中的所有節點
await
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //加入條件隊列
    Node node = addConditionWaiter();
    //釋放當前線程佔用的排它鎖  
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //節點不在AQS的阻塞隊列中
    while (!isOnSyncQueue(node)) {
        //阻塞該線程
        LockSupport.park(this);
        //判斷中斷標記在阻塞等待期間 是否改變  
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //當被喚醒後,該線程會嘗試去獲取鎖,只有獲取到了纔會從await()方法返回,否則的話,會掛起自己
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        //清理取消節點對應的線程 
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        //拋出中斷異常,或者重新進入中斷  
        reportInterruptAfterWait(interruptMode);
}

先將該節點加入到條件隊列,然後釋放掉當前的鎖,如果該節點不在AQS的阻塞隊列中就阻塞該線程,等待signal;被喚醒後該線程會嘗試去獲取鎖

signal
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //第一個節點
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        //firstWaiter 下一個節點
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
    //改變線程狀態
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    //加入AQS阻塞隊列
    Node p = enq(node);
    int ws = p.waitStatus;
    //喚醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

將條件隊列的第一個節點移除,加入到AQS的阻塞隊列中。

signalAll
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

signalAll 會遍歷全部節點喚醒加入到AQS阻塞隊列。

條件隊列與同步隊列

1.同步隊列依賴一個雙向鏈表來完成同步狀態的管理,當前線程獲取同步狀態失敗 後,同步器會將線程構建成一個節點,並將其加入同步隊列中。
2.通過signal或signalAll將條件隊列中的節點轉移到同步隊列。(由條件隊列轉化爲同步隊列)


條件隊列節點來源:

  1. 調用await方法阻塞線程;
  2. 當前線程存在於同步隊列的頭結點,調用await方法進行阻塞(從同步隊列轉化到條件隊列)

例如:

  1. 假設初始狀態如下,節點A、節點B在同步隊列中。

  1. 節點A的線程獲取鎖權限,此時調用await方法。節點A從同步隊列移除, 並加入條件隊列中。

  2. 調用 signal方法,從條件隊列中取出第一個節點,並加入同步隊列中,等待獲取資源

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