可重入鎖原始碼分析

pb_yan發表於2018-06-03

      ReentrantLocks意為可重入鎖,也就是獲得鎖的同一個執行緒可以多次獲得鎖而不會阻塞。但是同一時間內鎖只能被同一個執行緒持有,同時ReentrantLock又分為公平鎖和非公平鎖,但是他們都是通過維護一個節點佇列來實現,只不過公平鎖每次都取頭結點執行,而非公平鎖每次可能隨機取節點執行。無論那種情況,每當一個執行緒獲得鎖之後都會使狀態加一,當狀態為0時會通知後面排隊的節點(或者是在非公平鎖中某一個正好合適的執行緒執行)。



 /**
     * 一個抽象類,是鎖同步控制的基礎。子類有公平鎖和非公平鎖兩種。
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * 預設是非公平鎖.
         */
        abstract void lock();

        /**
         * 非公平鎖嘗試獲取資源.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//狀態為0,表示還沒有執行緒獲得鎖
                if (compareAndSetState(0, acquires)) {//CAS方式設定資源
                    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;//沒獲得資源,返回false
        }

        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;//釋放成功返回true
        }

        protected final boolean isHeldExclusively() {//當前執行緒是否是獲得了排他屬性的執行緒
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {//當前執行緒鎖的次數
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {//判斷是否有鎖
            return getState() != 0; 
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

   Syn的兩種實現方式:公平鎖和非公平鎖。公平鎖的語義和非公平鎖的語義不同在於,公平鎖每次從佇列的首部獲取等待最久的執行緒,而非公平鎖則每次隨機獲取執行緒執行,很可能某一個執行緒多次獲取鎖,導致其他執行緒飢餓。在下面程式碼中可以看出,非公平鎖首先使用CAS設定狀態,也就是說如果鎖是空就直接佔有,然後進入acquire進行處理。而公平鎖則直接進入acquire(1).

 /**
     * 非公平鎖
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))//CAS設定當前為0 的時候上鎖
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//否則嘗試獲得鎖。
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * 公平鎖
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * 
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {//沒有前驅節點並且CAS設定成功
                    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;
        }
    }

acquire中的詳細介紹。acquire是AQS類中的方法。其中tryAcquire就是(非)公平鎖類下的tryAcquire方法,若沒有獲得資源那麼就繼續執行,否則結束此函式。addWaiter用於新增節點,也就是把當前執行緒對應的節點插入CLH佇列的尾部。

 /**
     * 這個方法也就是lock()方法的關鍵方法。tryAcquire獲得資源,返回true,直接結束。若未獲取資源,新建一個節點插入隊尾,
     *
     * @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();
    }
/**
     * 為當前執行緒和模式建立一個節點,這個也是AQS中的類。
     *
     * @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;//設定pred為尾節點這裡嘗試一次。
        if (pred != null) {//尾節點不為空則設定新節點前驅為尾節點
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {//CAS原子操作將node設定為新的尾節點
                pred.next = node;
                return node;//pred下一個節點為新的尾節點。並返回新的尾節點
            }
        }
        enq(node);//上面沒成功,將節點插入隊尾
        return node;
    }
 /**
     *把一個節點插入隊尾中
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;//將尾節點賦值給t
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))//尾節點為空CAS設定新節點為頭結點
                    tail = head;
            } else {
                node.prev = t;//node 前置節點設定為t
                if (compareAndSetTail(t, node)) {//CAS操作設定node 為尾節點
                    t.next = node;
                    return t;//返回新的尾節點。
                }
            }
        }
    }
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;//表示當前執行緒在休眠過程中有沒有被中斷過
            for (;;) {//迴圈獲取前驅節點,一直到前驅是頭結點,並且某個節點獲取了資源
                final Node p = node.predecessor();//獲得node節點的前驅節點
                if (p == head && tryAcquire(arg)) {//p節點為頭結點,當前執行緒獲得了資源
                    setHead(node);//當前節點設定為頭結點,取消排隊
                    p.next = null; // 把node 設定為null
                    failed = false;
                    return interrupted;//返回是否中斷過
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//判斷當前執行緒是否應該阻塞
                    parkAndCheckInterrupt())//阻塞當期執行緒,在這裡執行park操作
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
 /**
     * 檢查自己在沒有獲得資源之後,是不是應該掛起。
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//獲得前置節點的狀態
        if (ws == Node.SIGNAL)
            /*
             * 如果前置節點的狀態是sigal,那麼就可以返回true,也就是意味著執行緒可以被阻塞了。
             */
            return true;
        if (ws > 0) {
            /*
             * 如果前置節點的狀態是刪除狀態,那麼就一直找到一個正常的狀態排在它後面。
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             *如果前驅正常,就把前驅狀態設定為sigal
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }


這裡簡單總結一下lock的過程:

   首先嚐試獲取資源,如果當前狀態為0,表示沒有執行緒佔有鎖,設定該執行緒為獨佔模式,使用CAS設定狀態,否則如果當前執行緒和獨佔執行緒是一個執行緒,修改狀態值,否則返回false。

  若獲取資源失敗,則通過addWaiter方法建立一個節點並放在CLH佇列的尾部。

  逐步去執行CLH佇列中的執行緒,當前執行緒會公平性的阻塞一直到獲取鎖為止,返回執行緒在等待的過程中還是否中斷過。



unLock方法比較簡單,直接release(1)即可 。

    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #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;
    }
    // 執行緒已被取消
    static final int CANCELLED =  1;
    // 當前執行緒的後繼執行緒需要被unpark(喚醒)
    // 一般發生情況是:當前執行緒的後繼執行緒處於阻塞狀態,而當前執行緒被release或cancel掉,因此需要喚醒當前執行緒的後繼執行緒。
    static final int SIGNAL    = -1;
    // 在Condition休眠狀態,在等待Condition喚醒
    static final int CONDITION = -2;
    // (共享鎖)其它執行緒獲取到“共享鎖”,對應的waitStatus的值
    static final int PROPAGATE = -3;

   protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();//持鎖執行緒和當前執行緒不一致丟擲異常
            boolean free = false;
            if (c == 0) {//狀態為0 說明鎖已經不被執行緒所佔有
                free = true;
                setExclusiveOwnerThread(null);//設定佔有鎖執行緒為null
            }
            setState(c);//設定鎖的狀態
            return free;
        }
/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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)//獲取當前節點狀態,若小於0則置狀態為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);//喚醒之
    }
 一次unlock操作需要修改狀態位,然後喚醒節點。整個釋放操作也是使用unpark()來喚醒佇列最前面的節點。其實lock中比較重要的也就是lock和release,它們又和AQS聯絡緊密,下面會單獨談談AQS的重要方法。




參考資料:

http://ifeve.com/juc-aqs-reentrantlock/

http://ifeve.com/java-special-troops-aqs/

http://www.cnblogs.com/skywang12345/p/3496147.html#p24

https://www.cnblogs.com/waterystone/p/4920797.html


相關文章