java併發系列之———AQS詳解

一,前言

AQS( AbstractQueuedSynchronizer 抽象隊列同步器 ) 是java juc併發工具類的基礎,本文主要將基於ReentrantLock可重入鎖的源碼結合講述AQS如何管理線程之間的協作

二,AQS基礎
1,AQS基本的數據結構是FIFO雙向隊列,如下圖:
在這裏插入圖片描述
(1)head tail

 /**
    * 頭結點,懶加載,該結點只能被setHead方法修改。並且結點的waitStatus不能爲CANCELLED
     */
    private transient volatile Node head;

    /**
      * 尾結點,懶加載,只有在enq方法中會加
     */
    private transient volatile Node tail;

(2) node狀態

  /** waitStatus表示該線程已經被刪除狀態 */
        static final int CANCELLED =  1;
        /** waitStatus表示該節點的前節點的線程需要被掛起 */
        static final int SIGNAL    = -1;
        /** waitStatus表示該線程等待 */
        static final int CONDITION = -2;
        /**waitStatus指示下一次acquireShared方法應該無條件傳播*/
        static final int PROPAGATE = -3;

2,AQS中維持了一個單一的volatile修飾的狀態信息state(AQS通過Unsafe的相關方法,以原子性的方式由線程去獲取這個state)。AQS提供了getState()、setState()、compareAndSetState()函數修改值(實際上調用的是unsafe的compareAndSwapInt方法)

 /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }
    
    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代碼結構
AQS使用模板模式,提供一些方法用於給繼承該類的子類重寫
獨佔式獲取
accquire
acquireInterruptibly
tryAcquireNanos
共享式獲取
acquireShared
acquireSharedInterruptibly
tryAcquireSharedNanos
獨佔式釋放鎖
release
共享式釋放鎖
releaseShared
需要子類覆蓋的流程方法
獨佔式獲取 tryAcquire
獨佔式釋放 tryRelease
共享式獲取 tryAcquireShared
共享式釋放 tryReleaseShared
這個同步器是否處於獨佔模式 isHeldExclusively

三,ReentrantLock介紹

ReentrantLock可重入鎖,實現Lock接口,代碼結構
有三個內部靜態類

abstract static class Sync extends AbstractQueuedSynchronizer{}//抽象類 
static final class NonfairSync extends Sync{}//非公平鎖繼承Sync類
static final class FairSync extends Sync{}//公平鎖繼承Sync類

在這裏插入圖片描述

一般使用方法:

Lock lock = new ReentrantLock();

可重入鎖分爲非公平鎖和公平鎖兩種,默認非公平鎖

  public ReentrantLock() {
        sync = new NonfairSync();
    }
 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

對於 ReentrantLock重寫了獨佔式獲取tryAcquire
接下來就ReentrantLock的讀取鎖過程對AQS源碼進行講解,以公平鎖爲例子。

三,公平鎖加鎖

final void lock() {
            acquire(1);
        }

1,acquire

acquire方法調用的是AQS類中的acquire方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            
            selfInterrupt();
    }
  1. 先嚐試調用tryAcquire方法獲取鎖
  2. 獲取不到,把線程打包成節點,放入隊列尾部

整個流程如下圖,我們按照此圖來講解
在這裏插入圖片描述

2,tryAcquire

tryAcquire方法源碼如下

  protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//獲取同步狀態,如果爲0表示沒有線程佔用
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    //hasQueuedPredecessors方法判斷是否有隊列爲空或者當前線程位於首節點,如果是(取反),
                    //則使用cas設置state值爲acquire =1,同時設置當前線程爲佔用該AQS的線程
                    return true;
                }
            }
          //如果不爲0,說明有線程佔用了,判斷佔用線程是否爲當前線程
            else if (current == getExclusiveOwnerThread()) {
            //線程重入,只需要在原來基礎的state加上acquire
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //返回否 表示都不滿足,有第三者線程佔用
            return false;
        }
    

看下hasQueuedPredecessors方法

 public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

看下doug大叔寫的這段判斷邏輯

  1. 如果h==t則說明隊列爲空。必然爲否,直接return false ,
  2. 如果h!=t則true && ??看後面判斷。
  3. (1)若首節點(頭結點的下一個節點)爲空,取true,必然後麪條件也取true,整體true&& true = true ;
  4. (2)若首節點不爲空,且線程爲當前線程,true && (false || false) = false;
  5. (3)若首節點不爲空,且線程不爲當前線程 則 true && (false || true) = true
  6. 則在1 4 情況下滿足條件

3,addWaiter

當tryAcquire爲否時,會調用addWaiter(Node.EXCLUSIVE)

  1. 先將當前線程打包成節點
  2. 如果隊列中有尾結點不爲空,嘗試將使用CAS設置該節點爲尾結點,成功直接return
  3. 不成功調用enq()方法,自旋設置該節點爲尾結點
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;
    }

enq()方法

  1. 方法自旋
  2. 如果無尾結點,cas設置頭結點,然後頭尾節點都指向一個new NODE()
  3. 注意設置節點爲尾結點流程爲:先將該節點prev節點設置爲當前尾結點,然後再CAS嘗試設置節點爲尾結點。注意:這裏使用unsafe的compareAndSwapObject方法直接寫內存,原子操作//TODO感覺這段CAS放進去有點問題,不是會覆蓋t麼
 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;
                }
            }
        }
    }
      /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

4,acquireQueued

當將當前線程放入隊列尾部後,調用acquireQueued方法,
方法流程如下

  1. 自旋
  2. 判斷當前節點的前節點如果爲頭節點,以及如果能獲取到鎖,則設置當前節點爲頭節點,return false,不調用selfInterrupt()方法
  3. 如果當前節點的前節點不是頭結點,或者競爭失敗,就會將node結點的waitStatus設置爲-1(SIGNAL),並且park自己,等待前驅結點的喚醒。
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);
        }
    }

回顧下節點waitStatus 的SIGNAL枚舉值(-1):告訴後續節點的線程需要進行unparking操作
shouldParkAfterFailedAcquire此方法解析:

  1. 如果前節點的狀態剛好是SIGNAL,則剛好可以表示後面節點需要進行線程掛起操作,return true
  2. 如果不是SIGNAL,且大於了0,表示節點狀態爲CANCELLED,爲需要刪除的節點,則一直沿着節點向上尋找可以合適掛靠的節點,當發現有節點的狀態小於等於0,則掛靠即:pred.next = node,其他可刪除節點就會被垃圾回收器回收,return false,
  3. 如果發現節點爲PROPAGATE(-3),CONDITION(-2),則設置該節點的狀態爲SIGNAL,return false
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

調用parkAndCheckInterrupt方法,即將當前節點的線程掛起,

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

注意代碼最後當如果沒有節點獲得鎖時,需要執行cancelAcquire()方法,取消正在進行的嘗試獲取鎖的操作

 private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            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)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

當最後獲取不到鎖則調用selfInterrupt()方法,設置當前線程掛起

 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

四,公平鎖解鎖

ReentrantLock解鎖調用unlock方法,則調用AQS的release方法,

 public void unlock() {
        sync.release(1);
    }

release方法先嚐試調用tryRelease方法,如果調用成功,則喚醒頭結點的下個節點

   public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease方法比較簡單,就是將取出當前同步器狀態然後減去1,由於存在可能鎖的重入情況,如果減到c==0,設置當前獨佔線程爲null,然後把值寫回同步器狀態

   protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

當tryRelease爲true後,尋找頭結點,調用unparkSuccessor方法,找到頭結點的後續節點狀態爲小於0的節點,調用 LockSupport.unpark方法,將節點從掛起喚醒。

 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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);
    }

五,參考文章

深入理解Java中的AQS

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