AQS框架


和Synchronized相比,可重入鎖ReentrantLock的實現原理有什么不同?

鎖的實現原理基本是為了達到一個目的:讓所有的線程都能看見某種標記
Synchronized是在對象頭中設置標記實現這一目的,是一種JVM原生鎖的實現.
ReentrantLock和其他所有的基於lock接口實現的類,都是通過一個volitile修飾的int型變量,並保證每個線程都能擁有對該int的可見性和原子修改。其本質是基於AQS框架實現的。

什么是AQS框架?

AQS(AbstractQueueSynchronizer類)是一個用來構建鎖和同步器的框架,各種Lock包中的鎖,甚至早起的FutureTask,都是基於AQS構建的。

1.AQS在背部定義了一個volatile int state變量,表示同步狀態:當線程調用Lock方法時,如果state=0,說明沒有任何線程占用共享資源鎖,可以獲得鎖並將狀態state改為1,;如果state=1,說明有線程正在使用共享變量,其他線程必須加入同步隊列並等待。

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

2.AQS通過Node內部類構成一個雙向鏈表結構的同步隊列,來完成線程獲取鎖的排隊工作,當有線程獲取鎖失敗后,就會被添加到隊列末尾。

     /**
     * Wait queue node class.
     *
     * <p>The wait queue is a variant of a "CLH" (Craig, Landin, and
     * Hagersten) lock queue. CLH locks are normally used for
     * spinlocks.  We instead use them for blocking synchronizers, but
     * use the same basic tactic of holding some of the control
     * information about a thread in the predecessor of its node.  A
     * "status" field in each node keeps track of whether a thread
     * should block.  A node is signalled when its predecessor
     * releases.  Each node of the queue otherwise serves as a
     * specific-notification-style monitor holding a single waiting
     * thread. The status field does NOT control whether threads are
     * granted locks etc though.  A thread may try to acquire if it is
     * first in the queue. But being first does not guarantee success;
     * it only gives the right to contend.  So the currently released
     * contender thread may need to rewait.
     *
     * <p>To enqueue into a CLH lock, you atomically splice it in as new
     * tail. To dequeue, you just set the head field.
     * <pre>
     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *      +------+       +-----+       +-----+
     * </pre>
     *
     * <p>Insertion into a CLH queue requires only a single atomic
     * operation on "tail", so there is a simple atomic point of
     * demarcation from unqueued to queued. Similarly, dequeuing
     * involves only updating the "head". However, it takes a bit
     * more work for nodes to determine who their successors are,
     * in part to deal with possible cancellation due to timeouts
     * and interrupts.
     *
     * <p>The "prev" links (not used in original CLH locks), are mainly
     * needed to handle cancellation. If a node is cancelled, its
     * successor is (normally) relinked to a non-cancelled
     * predecessor. For explanation of similar mechanics in the case
     * of spin locks, see the papers by Scott and Scherer at
     * http://www.cs.rochester.edu/u/scott/synchronization/
     *
     * <p>We also use "next" links to implement blocking mechanics.
     * The thread id for each node is kept in its own node, so a
     * predecessor signals the next node to wake up by traversing
     * next link to determine which thread it is.  Determination of
     * successor must avoid races with newly queued nodes to set
     * the "next" fields of their predecessors.  This is solved
     * when necessary by checking backwards from the atomically
     * updated "tail" when a node's successor appears to be null.
     * (Or, said differently, the next-links are an optimization
     * so that we don't usually need a backward scan.)
     *
     * <p>Cancellation introduces some conservatism to the basic
     * algorithms.  Since we must poll for cancellation of other
     * nodes, we can miss noticing whether a cancelled node is
     * ahead or behind us. This is dealt with by always unparking
     * successors upon cancellation, allowing them to stabilize on
     * a new predecessor, unless we can identify an uncancelled
     * predecessor who will carry this responsibility.
     *
     * <p>CLH queues need a dummy header node to get started. But
     * we don't create them on construction, because it would be wasted
     * effort if there is never contention. Instead, the node
     * is constructed and head and tail pointers are set upon first
     * contention.
     *
     * <p>Threads waiting on Conditions use the same nodes, but
     * use an additional link. Conditions only need to link nodes
     * in simple (non-concurrent) linked queues because they are
     * only accessed when exclusively held.  Upon await, a node is
     * inserted into a condition queue.  Upon signal, the node is
     * transferred to the main queue.  A special value of status
     * field is used to mark which queue a node is on.
     *
     * <p>Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill
     * Scherer and Michael Scott, along with members of JSR-166
     * expert group, for helpful ideas, discussions, and critiques
     * on the design of this class.
     */
    static final class Node {
    ……
    }
  • Node類是對要訪問同步代碼線程的封裝,包含線程本身及其狀態waitStatus(有五種取值:是否被阻塞,是否等待喚醒,是否已經被取消等),每個Node節點關聯其prev節點和next節點,方便線程釋放鎖后幻想下一個等待的線程,是一個FIFO的過程。
        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;
  • Node類有兩個常量,SHARED和EXCLUSIVE,分別代表共享模式和獨占模式。所謂共享模式就是一個鎖熏暈多個線程同事操作(信號量Semaphore就是AQS的共享模式實現的),獨占模式是同一個時間段只能有一個線程對共享資源進項操作,多余的線程需要排隊等待(如ReentranLock)。
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

3.AQS通過內部類ConditionObject構建等待隊列(可有多個),當Condition調用wait()后,線程將會加入等待隊列中,Condition調用signal()后,線程會從等待隊列中轉移到同步隊列中進行鎖競爭。

4.AQS和Condition各自維護不同的隊列,在使用Lock和Condition的時候,其實就是兩個隊列在互相移動。

Synchronized和ReentrantLock的異同

ReentrantLock是Lock的實現類,是一個互斥的同步鎖。

功能角度講:ReentrantLock比synchronized更精細,可以實現synchronized實現不了的功能:

  • 等待可中斷:當長期持有鎖的線程不釋放鎖時,正在等待的線程可以選擇放棄等待,對處理執行時間非常長的同步塊很有用。
  • 帶超時的獲取鎖嘗試:在指定時間范圍內獲取鎖,如果到了時間仍無法獲取就返回。
  • 可以判斷是否有線程在等待獲取鎖。
  • 可以響應中斷請求:與synchronized不同,當獲取到鎖的線程被中斷時,能夠響應中斷,中斷異常會被拋出,同時鎖會被釋放。
  • 可以實現公平鎖

釋放鎖的角度講:synchronized在JVM層面實現的,不但可以通過監控工具監測synchronized的鎖定,代碼異常時會自動釋放鎖;Lock加鎖后必須手動釋放鎖。

性能角度講:java6改進synchronized后,在競爭不激烈的情況下,synchronized性能高於ReentrantLock;高競爭情況下,synchronized性能會下降幾十倍,ReentrantLock性能會維持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM