AQS原始碼分析

辣雞小籃子發表於2020-09-05

      AbstractQueuedSynchronized(AQS)是一個抽象的佇列同步器,它本身是一個抽象類,提供一個FIFO的等待佇列來存放阻塞資源和一個state狀態來儲存鎖的資訊。在實際的使用中,比如ReentrantLock和ReentrantReadWirteLock中,它們本身都有一個Syn類來繼承AQS並做一些狀態操作。

      鎖的使用過程大概如下,在實際使用鎖的過程中,比如我們使用一個lock()函式,同步類可以幫助我們通過tryAcquire來獲取鎖,如果可以獲取則state+1,如果不可以則新建節點放入同步佇列中,然後把這個執行緒阻塞。當我們使用一個realease()函式時,如果執行緒是持有鎖的執行緒則狀態減一,如果狀態為0則喚醒後繼節點。這裡強調一下lock的作用,其實就是用來分配狀態和阻塞節點,而unlock就是釋放狀態和喚醒節點

   AQS中程式碼結構如下,Node代表的是我們所說的佇列中的節點。ConditionObject中放的是有關Condition相關的實現方法。和Lock(unLock)相關的函式包括兩種,獨佔鎖函式和共享鎖函式,典型的就是ReentrantLock和ReentrantReadWirteLock。

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;

        /**
         * 等待狀態,前面四個,還有一個值是0,表示不是上面的四個。
         */
        volatile int waitStatus;

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

        /**
         *後集結點
         */
        volatile Node next;

        /**
         * 節點中放的是執行緒
         */
        volatile Thread thread;   
      

   

Condition

     Condition在AQS中具體實現了,在其他的同步類中我們可以直接使用它,通過設定一個條件,在合適的時候通過呼叫await使一個執行緒沉睡並釋放鎖,當其他執行緒呼叫singal方法時會喚醒那個執行緒。我們通常可以將condition視為一個多執行緒之間通訊的工具。

     Condition本身也維護一個等待條件佇列,整個await過程如下:

      首先將該執行緒對應的節點放進條件等待佇列中去,然後釋放執行緒持有的全部鎖,判斷節點是否還在AQS的佇列中(這裡沒有被singal前是沒有被在佇列中的),因此這裡這個執行緒是會被掛起的。當後面signal方法被執行時,這裡在條件佇列中的執行緒還是會重新放在AQS的佇列中去。

 /**
         * Implements interruptible condition wait.
         * <ol>
         * <li> If current thread is interrupted, throw InterruptedException.
         * <li> Save lock state returned by {@link #getState}.
         * <li> Invoke {@link #release} with saved state as argument,
         *      throwing IllegalMonitorStateException if it fails.
         * <li> Block until signalled or interrupted.
         * <li> Reacquire by invoking specialized version of
         *      {@link #acquire} with saved state as argument.
         * <li> If interrupted while blocked in step 4, throw InterruptedException.
         * </ol>
         */
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//將節點放到condition佇列中去
            int savedState = fullyRelease(node);//釋放所有資源返回資源數目
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//不在AQS佇列中,阻塞
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//獲取自己之前所有的資源
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

 

    signal方法用於喚醒在條件佇列中頭結點的執行緒,如果條件佇列中的頭結點不為空,那麼就執行doSingal()函式。維護條件佇列中的節點,將頭結點放入AQS尾部,等待自己釋放鎖後,原來的await執行緒就可以獲取資源執行了。

 

 /**
         * Moves the longest-waiting thread, if one exists, from the
         * wait queue for this condition to the wait queue for the
         * owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }


         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&//將頭結點放到AQS
                     (first = firstWaiter) != null);
        } 
 /**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal)
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);//把節點放進AQS的尾部
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//喚醒節點,進行資源搶佔
            LockSupport.unpark(node.thread);//這裡其實狀態並不正常
        return true;  }

 

acquire()

 

    獨佔鎖模式下可以通過這個函式來獲取鎖,獲取獨佔鎖的過程是這樣的。首先使用tryAcquire函式,這個函式在AQS中只是一個介面,具體實現在其實現類中實現,可以參照前面reentrantLock中的實現,主要作用是用來獲取資源,如果獲取到資源直接返回。否則為該執行緒新設定一個節點然後放到佇列後面,然後在佇列中找到頭結點後面的節點,然後判斷此過程中是否需要阻塞該執行緒。過程如下:

  

具體過程可以看reentrantLock分析。

 /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

release()

獨佔模式下釋放資源,執行tryRealease(1)嘗試釋放鎖,如果執行緒不一致則丟擲異常,否則狀態為0 則返回true,喚醒後繼節點。

public final boolean release(int arg) {  
    if (tryRelease(arg)) {//嘗試釋放鎖,其實就是狀態減一。  
        Node h = head;  
        if (h != null && h.waitStatus != 0)  
            unparkSuccessor(h);//成功則喚醒後繼節點  
        return true;  
    }  
    return false;  
}  

 

acquiredShare()

 

   共享鎖的情況下同樣是要維護一個佇列和一個狀態,但是在這裡把state分成兩個部分,高16為用於表示讀鎖的狀態,低16為用來表示寫鎖的狀態。

共享模式下獲得鎖的過程比較複雜,它的過程簡單描述如下:

 

 /**
     * 忽略中斷,以共享的模式獲取鎖  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)//嘗試獲取鎖,如果沒有獲得,就做一些嘗試操作
            doAcquireShared(arg);//失敗則進入等待佇列一直到獲取資源為止
    }

 

releaseShared()

 

共享模式下釋放資源分為兩步,首先嚐試釋放共享資源,如果成功則進行後續的喚醒操作。否則的話直接失敗。

 /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

   整體來講,AQS最重要的函式以及流程就是上面這幾個,其他的比如tryLock或者是帶時間的tryLock都是類似的。這裡就不展開贅述了。

 

 

參考資料:

http://ifeve.com/understand-condition/

https://blog.csdn.net/pb_yan/article/details/80572194

http://ifeve.com/jdk1-8-abstractqueuedsynchronizer

http://ifeve.com/introduce-abstractqueuedsynchronizer/

https://blog.csdn.net/HEYUTAO007/article/details/49889849

相關文章