AbstractQueuedSynchronizer源碼分析之共享鎖實現

 

doAcquireShared()方法

該方法在共享模式以不響應中斷的方式阻塞等待獲取鎖,實現如下:
1、將當前線程封裝成節點入隊;
2、在死循環中調用park方法。第一次循環(自旋、acquire loop),或者被喚醒從park方法返回後,會判斷前驅節點是否是頭節點,以及調用tryAcquire()方法是否返回true,如果這2個條件都爲真,由當前節點自己設置爲頭節點,並將後繼節點喚醒,然後return;退出死循環。如果這2個條件不滿足,會繼續調用park方法阻塞等待。
3、在第二步中,被喚醒從park方法返回後,有一個額外操作就是會判斷線程的中斷狀態,如果中斷狀態爲true,僅僅是設置中斷標誌位interrupted,不拋出中斷異常。

4、在第二步中,在判斷那2個條件不滿足,和調用park方法阻塞等待之間,還有一個操作就是判斷在獲取失敗後是否應該調用park方法阻塞等待。即shouldParkAfterFailedAcquire方法。在acquire loop中會不斷調用該方法retry,使該方法最終總是趨向於返回true。

/**
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//將當前線程封裝成節點入隊
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {   //死循環
                final Node p = node.predecessor();
                if (p == head) { //如果前驅節點是頭節點
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r); //當前節點設置爲頭節點,並喚醒後繼節點
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;  //退出死循環
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&   //獲取失敗後判斷是否應該park阻塞等待
                    parkAndCheckInterrupt())  //調用park方法阻塞等待,park方法返回後判斷是否中斷
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

doAcquireSharedInterruptibly方法

該方法在共享模式以響應中斷的方式阻塞獲取鎖。實現邏輯與doAcquireShared方法是基本相同的。不同的是在從park方法返回後,如果判斷線程的中斷狀態爲true,會拋出中斷異常。

 /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailAcquire方法

該方法在當前節點獲取失敗時判斷是否應該使線程阻塞等待。它會檢查和更新前驅結點的狀態。在acquire loop中會不斷調用該方法retry,使最終總是趨向於返回true:

1、剛入隊的節點,他們的waitStatus都是int數據類型的初始值0。

2、對於剛入隊的節點,它的前驅節點的waitStatus,要麼是0,要麼是propagate,要麼是cancelled。如果前驅節點的waitStatus是0,在第一次acquire loop中,會使用cas將前驅節點的waitStatus設置爲SIGNAL。如果前驅節點的waitStatus是propagate,說明該前驅節點是head節點,在第一次acquire loop中,會使用cas將前驅節點的waitStatus設置爲SIGNAL。如果前驅節點的waitStatus是cancelled,會從後遍歷直至找到有效的前驅節點。雖然在第一次acquire loop中調用shouldParkAfterFailAcquire方法,它的返回值是false,但是在多次acquire loop後,前驅節點的waitStatus總爲SIGNAL,該方法的返回值總是true。

/**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @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)
            /*
             * 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方法

調用park方法阻塞等待,park方法返回後判斷是否中斷

/**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

setHeadAndPropagate方法

將當前節點設置爲頭節點,並將後繼節點喚醒,頭節點的waitStatus設爲propagate。

 /**
     * Sets head of queue, and checks if successor may be waiting
     * in shared mode, if so propagating if either propagate > 0 or
     * PROPAGATE status was set.
     *
     * @param node the node
     * @param propagate the return value from a tryAcquireShared
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();  //將後繼節點喚醒,頭節點的waitStatus設爲propagate
        }
    }

doReleaseShared方法

將後繼節點喚醒,頭節點的waitStatus設爲propagate。

在死循環內:

1、獲取head節點的waitStatus;

2、如果head節點的waitStatus爲SIGNAL,通過cas將head節點的waitStatus改爲0,必須確保cas執行成功,否則通過新一輪循環,再次執行cas操作直至成功爲止;(死循環與cas構成自旋鎖保證cas執行成功)

3、如果第二步執行成功,調用unparkSuccessor方法喚醒後繼節點;

4、第三步執行完成後,開始新一輪的循環,判斷如果head節點的waitStatus爲0,通過cas將head節點的waitStatus改爲propagate,必須確保cas執行成功,否則通過新一輪循環,再次執行cas操作直至成功爲止;(死循環與cas構成自旋鎖保證cas執行成功)

由此可見,head節點的waitStatus爲SIGNAL時,通過兩步改爲propagate:

compareAndSetWaitStatus(h, Node.SIGNAL, 0)

compareAndSetWaitStatus(h, 0, Node.PROPAGATE)

爲什麼要經過兩步,不直接把SIGNAL改爲propagate呢?原因在unparkSuccessor方法。如果直接把SIGNAL改爲propagate,則在unparkSuccessor方法裏又會被設置爲0。

5、在第四步完成後,判斷當前head節點是否發生改變,如果沒有發生改變,break退出死循環。在第三步喚醒後繼節點後,後繼節點(所在的線程)會將自己設置爲頭節點,此時head節點就會發生改變,對新head節點繼續執行循環,從而實現release propagate。有細心的網友可能就發現了,新head節點也會調用到doReleaseShared方法,這樣會存在多個線程同時調用doReleaseShared方法,執行死循環裏的邏輯。是的,代碼註釋也說明了:

Ensure that a release propagates, even if there are other in-progress acquires/releases.

 /**
     * Release action for shared mode -- signals successor and ensures
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

unparkSuccessor方法

如果節點的waitStatus爲負,通過cas設置爲0;找到有效的後繼節點,調用unpark方法

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

 

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