聊聊ReentrantLock的內部實現

  你們都用過ReentrantLock,可是你們對內部實現是否足夠了解呢,下面我就簡單說一下其中的實現原理。node

  ReentrantLock是可重入鎖,也就是同一個線程能夠屢次獲取鎖,每獲取一次就會進行一次計數,解鎖的時候就會遞減這個計數,直到計數變爲0。app

  它有兩種實現,一種是公平鎖,一種是非公平鎖,那麼默認是什麼鎖呢?看完以下代碼想必你也知道了。  less

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

  它的內部結構的實現是如何的呢? 首先NonFairSync類是靜態內部類,它繼承了Sync。ui

/**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync

  Sync繼承了AbstractQueuedSynchronizer,簡稱AQS。同時Sync裏邊實現了tryRelease方法,由於公平鎖和非公平鎖均可以用這個方法釋放鎖。this

/**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer 

  繼續看非公平鎖的lock方法,採用CAS進行當前狀態的設置state=0,表示沒有線程佔用,state=1表示已經有現成佔用了,設置成功了,將當前線程設置爲線程擁有者,而且是排他的。若是有現成佔用了,那麼須要進入acquire(1),須要獲取一個鎖。spa

/**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

  acquire方法首先進行tryAcquire,嘗試獲取鎖,即調用nonfairTryAcquire,判斷當前鎖是否state=0, 則沒有現成佔用,則進行設置。若是被佔用了判斷該線程是不是當前線程佔用的,若是是的話,那麼能夠進行重入,即當前能夠獲取鎖,計數器進行加1。不然的話返回失敗。返回失敗後執行addWaiter方法,也就是添加到等待的隊列。Node是一個雙向列表,也就是把須要等待的線程放到放到Node,而且連接起來。線程

            /**
     * 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();
    }

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        
    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    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;
    }

  接下來看一下Node的大體內容,有兩個指針,一個是prev,一個是next,還保存着當前的線程。同時裏邊還有一個共享鎖和獨佔鎖,SHARED和EXCLUSIVE。ReentrantLock採用的就是獨佔鎖。Semaphore,CountDownLatch等採用的是共享鎖,即有多個線程能夠同時獲取鎖。指針

static final class Node {
        /** 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;
        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

  大體的思路咱們看了一下,整體的流程圖我畫了一下。ReentrankLock內核採用的是AQS實現的,AQS裏邊採用的是雙向鏈表,即若是當前線程未獲取到鎖將會加入到鏈表中。code

  那麼公平鎖和非公平鎖的實現的不一樣點在哪裏呢?公平鎖和非公平鎖就差在 !hasQueuedPredecessors() ,也就是前邊沒有排隊者的話,我就能夠獲取鎖了。orm

/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  若是當前線程以前還有線程等待就會返回true,若是當前節點是頭結點,或者當前隊列爲空就會返回false。非公平鎖沒有這句話的判斷,因此直接去競爭鎖。

   /**
     * Queries whether any threads have been waiting to acquire longer
     * than the current thread.
     *
     * <p>An invocation of this method is equivalent to (but may be
     * more efficient than):
     *  <pre> {@code
     * getFirstQueuedThread() != Thread.currentThread() &&
     * hasQueuedThreads()}</pre>
     *
     * <p>Note that because cancellations due to interrupts and
     * timeouts may occur at any time, a {@code true} return does not
     * guarantee that some other thread will acquire before the current
     * thread.  Likewise, it is possible for another thread to win a
     * race to enqueue after this method has returned {@code false},
     * due to the queue being empty.
     *
     * <p>This method is designed to be used by a fair synchronizer to
     * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
     * Such a synchronizer's {@link #tryAcquire} method should return
     * {@code false}, and its {@link #tryAcquireShared} method should
     * return a negative value, if this method returns {@code true}
     * (unless this is a reentrant acquire).  For example, the {@code
     * tryAcquire} method for a fair, reentrant, exclusive mode
     * synchronizer might look like this:
     *
     *  <pre> {@code
     * protected boolean tryAcquire(int arg) {
     *   if (isHeldExclusively()) {
     *     // A reentrant acquire; increment hold count
     *     return true;
     *   } else if (hasQueuedPredecessors()) {
     *     return false;
     *   } else {
     *     // try to acquire normally
     *   }
     * }}</pre>
     *
     * @return {@code true} if there is a queued thread preceding the
     *         current thread, and {@code false} if the current thread
     *         is at the head of the queue or the queue is empty
     * @since 1.7
     */
    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());
    }

  這個就是ReentrantLock的基本原理,接下來我們繼續看看與之一塊使用的Condition。Condition是一個接口,它的實現類是ConditionObject。調用await的時候也會將當前線程的一些信息加入到隊列當中。ConditionObject中有一個firstWaiter和LastWaiter分別指向的了等待隊列的頭和尾。

  當調用Condition的signal方法是,則會將第一個Node轉換到同步隊列,以下圖所示。

  好了,總結一下:

  1. ReentrankLock默認是非公平鎖。

  2.ReentrankLock的內部實現採用的AQS的雙向鏈表實現。獲取鎖的線程會被封裝成Node裏邊,供後續使用。

  3.公平鎖採用判斷當前Node是否是頭結點,若是是的話就獲取鎖並作業務處理,不是頭結點的不能獲取所。

  4.非公平鎖沒有判斷當前結點,採用CAS,誰第一個拿到了state=0,則視爲獲取鎖。

  5.Condition的await和notify也採用相似的機制,當執行await是,會將當前線程信息的相關信息放入到Node的列表,記錄firstWaiter和lastWaiter指向的信息。

  

  但願對你們有所幫助,若是有問題的請及時指出。

相關文章
相關標籤/搜索