JUC之CountDownLatch的原始碼和使用場景分析

cmazxiaoma發表於2019-02-25

最近工作不飽和,寫寫文章充充電。何以解憂,唯有Coding。後續更新的文章涉及的方向有:ThreadPoolExecutor、Spring、MyBatis、ReentrantLock、CyclicBarrier、Semaphore.

同系列文章: 1.看ThreadPoolExecutor原始碼前的騷操作

saoqi.png


開始講解之前,自定義ThreadPoolExecutor和Task。

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {


    public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public static class CustomTask<V> extends FutureTask<V> {

        public CustomTask(Callable<V> callable) {
            super(callable);
        }

        public CustomTask(Runnable runnable, V result) {
            super(runnable, result);
        }

    }

}
複製程式碼
  • 為什麼要需要執行緒池 ? 執行緒資源必須通過執行緒池提供,不允許在應用中自行顯式的建立執行緒。使用執行緒池的好處是減少在建立和銷燬執行緒上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用執行緒池的話,有可能造成系統建立大量的同類執行緒而消耗完執行緒而導致消耗完記憶體或者過度切換的問題。

  • 為什麼要不能通過Executors去建立執行緒池? 因為FixedThreadPool和SingleThreadPool使用的是無界佇列,會堆積大量的請求,造成OOM。還有CachedThreadPool和ScheduledThreadPool會造成Integer.MAX_VALUE,會建立大量的執行緒,造成OOM


分析CountDownLatch

CountDownLatch用於協調多個執行緒的同步,能讓一個執行緒在等待其他執行緒執行完任務後,再繼續執行。內部是通過一個計數器去完成實現。

靜態內部類Sync繼承AQS,通過state變數完成計數器的實現。

    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
複製程式碼

CountDownLatch構造方法,count代表需要執行的執行緒數量。

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
複製程式碼

在計數值 > 0的情況下,每當一個執行緒完成任務,計數減去1。

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }
複製程式碼

讓當前執行緒等待其他執行緒,直到計數為0,除非當前執行緒被中斷了。

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
複製程式碼

讓當前執行緒等待其他執行緒,如果超過指定的timeout時間範圍,那麼忽略要等待的執行緒, 直接執行。

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
複製程式碼

返回當前計數量。

    /**
     * Returns the current count.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the current count
     */
    public long getCount() {
        return sync.getCount();
    }
複製程式碼

當計數器應該為0,所有的執行緒執行完自己的任務。在CountDownLatch等待的執行緒,可以繼續執行的任務。


使用場景

適用於一個任務的執行需要等待其他任務執行完畢,方可執行的場景。

場景1:一群學生在教室考試,學生們都完成了作答,老師才可以進行收卷操作。

場景2:110跨欄比賽中,所有運動員準備好起跑姿勢,進入到預備狀態,等待裁判一聲槍響。裁判開了槍,所有運動員才可以開跑。

CountDownLatch是一次性的,只能通過構造方法設定初始計數量,計數完了無法進行復位,不能達到複用。而CyclicBarrier可以實現複用。


場景1

一個執行緒的執行要等待其他執行緒執行完畢後,才能繼續執行。

    public static void test1() {
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        ExecutorService executorService = new CustomThreadPoolExecutor(2,
                2, 0L,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));

        for (int i = 0; i < 2; i++) {
            CustomThreadPoolExecutor.CustomTask task = new CustomThreadPoolExecutor.CustomTask(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子執行緒" + Thread.currentThread().getName()
                            + "正在執行...");
                    System.out.println("子執行緒" + Thread.currentThread().getName()
                            + "執行完畢...");
                    countDownLatch.countDown();
                }
            }, "success");
            executorService.submit(task);
        }

        try {
            System.out.println("等待2個執行緒...");
            countDownLatch.await();
            executorService.shutdown();
            System.out.println("2個執行緒執行完畢...");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
複製程式碼

輸出結果

等待2個執行緒...
子執行緒pool-1-thread-2正在執行...
子執行緒pool-1-thread-2執行完畢...
子執行緒pool-1-thread-1正在執行...
子執行緒pool-1-thread-1執行完畢...
2個執行緒執行完畢..
複製程式碼

場景2

多個執行緒在某一個時刻同時執行。

    public static void test2() {
        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch end = new CountDownLatch(10);

        ExecutorService executorService = new CustomThreadPoolExecutor(10,
                10, 0L,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));

        for (int i = 0; i < 10; i++) {
            CustomThreadPoolExecutor.CustomTask task = new CustomThreadPoolExecutor.CustomTask(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("子執行緒" + Thread.currentThread().getName()
                                + "正在執行...");
                        start.await();
                        System.out.println("子執行緒" + Thread.currentThread().getName()
                                + "執行完畢...");
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    } finally {
                        end.countDown();
                    }
                }
            }, "success");
            executorService.submit(task);
        }

        start.countDown();
        try {
            System.out.println("等待10個執行緒...");
            end.await();
            executorService.shutdown();
            System.out.println("10個執行緒執行完畢...");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
   
複製程式碼

輸出結果

子執行緒pool-1-thread-2正在執行...
子執行緒pool-1-thread-1正在執行...
子執行緒pool-1-thread-3正在執行...
子執行緒pool-1-thread-4正在執行...
子執行緒pool-1-thread-5正在執行...
子執行緒pool-1-thread-6正在執行...
子執行緒pool-1-thread-7正在執行...
等待10個執行緒...
子執行緒pool-1-thread-2執行完畢...
子執行緒pool-1-thread-8正在執行...
子執行緒pool-1-thread-8執行完畢...
子執行緒pool-1-thread-1執行完畢...
子執行緒pool-1-thread-3執行完畢...
子執行緒pool-1-thread-4執行完畢...
子執行緒pool-1-thread-10正在執行...
子執行緒pool-1-thread-10執行完畢...
子執行緒pool-1-thread-5執行完畢...
子執行緒pool-1-thread-6執行完畢...
子執行緒pool-1-thread-7執行完畢...
子執行緒pool-1-thread-9正在執行...
子執行緒pool-1-thread-9執行完畢...
10個執行緒執行完畢...
複製程式碼

CountDownLatch原始碼分析

CountDownLatch的構造方法初始計數器值,是通過其內部類Sync的構造方法來實現的。

        Sync(int count) {
            setState(count);
        }
複製程式碼

AQS中的state變數可以表示狀態。對於ReentrantLock而言,代表著鎖獲取的次數。而對於CountDownLatch代表著計數器的值。state變數通過volatile修飾,具有可見性,可以在多個執行緒中共享變數。

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }
複製程式碼

AQS中的CAS操作,使其state變數具有原子性。

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }
複製程式碼

我們來看一下AQS中的Node節點結構。

  • 當waitStatus = CANCELLED時,說明因為超時或者中斷,節點會被設定為取消狀態。處於取消狀態的節點不會參與到競爭中。它會一直保持取消狀態,會轉變到其他狀態。
  • 當waitStatus = SIGNAL時,說明當前節點的後繼節點處於等待狀態。而當前節點的執行緒如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的執行緒可以得到執行。
  • 當waitStatus = CONDITION,說明該節點在等待佇列中,節點執行緒等待在Condition上。當其他執行緒對Condition呼叫了signal()後,該節點會從等待佇列中轉移到同步佇列中,加入到同步狀態的獲取中。
  • 當waitStatus = PROPAGATE,說明下一次共享式獲取同步狀態,將會無條件的傳播下去。
static final class 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;

    /** 等待狀態 */
    volatile int waitStatus;

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

    /** 後繼節點 */
    volatile Node next;

    /** 獲取同步狀態的執行緒 */
    volatile Thread thread;

    Node nextWaiter;

    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {
    }

    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}
複製程式碼

countDown()實際是呼叫AQS中的releaseShared方法中,達到計數減1的目的。

    public void countDown() {
        sync.releaseShared(1);
    }
複製程式碼

countDown主要流程(來自於網際網路).png

以共享模式去釋放鎖,如果tryReleaseShared方法釋放鎖成功,則執行AQS中的doReleaseShared方法去喚醒等待執行緒,並且返回true;否則返回false,說明鎖釋放失敗。

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
複製程式碼

tryReleaseShared在CountDownLatch中被重寫。通過輪詢 + CAS方式達到釋放鎖的目的。第一次迴圈的時候判斷當前state變數,如果等於0,說明計數器值為0或者說鎖沒有被持有,可以直接返回false。然後進行CAS操作,讓獲取鎖的次數減少1或者說計數器值減少1。如果nextc等於0,說明計數值為0或者持有鎖的次數為0,可以讓喚醒等待的執行緒,所以返回true,否則返回false,代表釋放鎖失敗。

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
複製程式碼

首先獲得head節點。如果head節點不等於空且head節點不等於tail節點,獲得head節點的waitStatus。判斷當前head節點狀態是否是SINGAL。處於SINGAL狀態的節點,說明當前節點的後繼節點處於被喚醒的狀態。如果CAS操作將head節點的waitStatus重置為0失敗,那麼跳出當前迴圈,繼續執行下一次迴圈(重新檢查)。如果重置成功,那麼呼叫unparkSuccessor方法喚醒後繼節點。 如果當前head節點狀態等於0,通過CAS操作將waitStatus設定為PROPAGATE(傳播)狀態,確保可以向後一個節點傳播下去。如果CAS操作失敗,那麼當前迴圈,繼續執行下一次迴圈。最後的h == head,是判斷head節點是否發生變化。如果沒有發生變化,結束迴圈。如果發生變化,必須再次迴圈。

    /**
     * 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;
        }
    }
複製程式碼

獲取head節點的waitStatus,如果小於0,進行CAS操作重置為0。獲取head節點的後繼節點,如果後繼節點等於null或者後繼節點的waitStaus大於0(說明後繼節點處於CANCELLED狀態),那麼從佇列從尾部往前進行遍歷尋找waitStatus小於等於0的節點。如果這個遍歷出來的節點不等於null的話,那麼通過LockSupport.unpark()喚醒這個節點中的執行緒。

    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);
    }
複製程式碼

分析完了CountDownLatch的countDown()方法,接著擼await()方法

await主要流程(來自於網際網路).png

await方法會使當前執行緒在計數器值為0之前,一直處於等待狀態,除非中斷。

   public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
複製程式碼

AQS中的acquireSharedInterruptibly方法,會判斷執行緒是否中斷。如果中斷, 丟擲InterruptedException異常。值得注意的是Thread.interrupted()方法,是測試當前執行緒是否中斷。該方法會清除執行緒的中斷狀態。換句話說,如果呼叫這個方法2次,那麼第二次會直接返回false,除非當前執行緒在第一次呼叫之後再次被中斷。如果tryAcquireShared()小於0(說明該計數器值大於0),繼續執行doAcquireSharedInterruptibly。

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
複製程式碼

在CountDownLatch中重寫了AQS中的tryAcquireShared()方法,這裡只是簡單的判斷state變數。如果state等於0(說明計數值為0),返回1,否則返回-1(說明計數器值大於0)。

 protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

複製程式碼

很明顯,是通過輪詢的方式去獲取共享鎖。首先將當前執行緒包裝成型別為SHARED的節點,標誌為共享型別的節點。獲取當前節點的前驅節點。如果當前節點的前驅節點為head節點的話,說明該節點是在AQS佇列中等待獲取鎖的第一個節點。呼叫CountDownLatch中的tryAcquireShared()嘗試去獲取鎖。返回的值大於0的話,說明獲取鎖成功。如果獲取共享鎖成功,那麼把當前節點設定為AQS同步佇列中的head節點,同時將p.next置為null(方便GC)。回到頭看,如果當前節點的前驅節點不是head節點或者獲取鎖失敗,我們需要呼叫shouldParkAfterFailedAcquire()方法判斷當前執行緒是否需要掛起,如果需要掛起呼叫 parkAndCheckInterrupt()

    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);
        }
    }
複製程式碼

通過當前執行緒構造出Node節點,mode用於標誌是獨佔還是共享。如果佇列非空,則快速入隊。通過CAS將node節點置為tail節點,並返回node節點。如果CAS失敗或者佇列為空,那麼通過enq()入隊。

    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;
    }
複製程式碼

enq中通過死迴圈的方式保證節點的正確插入。如果佇列為空,那麼建立一個新的節點。通過CAS將新節點設定為head節點,同時也將head節點設定為tail節點。當佇列只有一個元素時,head節點等於tail節點。在迴圈中,唯一跳出迴圈的條件是通過CAS將node節點設定為tail節點。這樣的話,enq方法將併發插入節點的請求變得序列化了。

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
複製程式碼

這裡將node節點設定為head節點。經過一些條件判斷後,獲取head節點的後繼節點。如果後繼節點等於null或者後繼節點也是共享節點,那麼呼叫doReleaseShared去喚醒它。

    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();
        }
    }
複製程式碼

如果pred節點(node節點的前驅節點)的狀態是SIGNAL,說明該pred節點的執行緒如果釋放了同步狀態或者被取消,會通知其後繼節點(也就是node節點)。所以我們可以安全讓node節點的執行緒掛起。如果pred節點處於取消狀態,我們進行死迴圈, 直到pred節點的狀態不是取消狀態。通過死迴圈,我們能確保node節點的前驅節點不處於取消狀態。反之,如果一開始pred節點不處於取消狀態,那麼我們通過CAS將pre節點的狀態置為SIGNAL,為後面迴圈涉及到的park操作進行準備。但是有一點要注意,我們進行park之前,要確保當前節點獲取鎖失敗。

   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;
    }
複製程式碼

呼叫LockSupport.park()掛起當前執行緒,並且返回中斷狀態。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
複製程式碼

CountDownLatch的await(long timeout, TimeUnit unit)和await()方法實現方式基本一致,只不過加入了超時機制而已。

   // 返回false,代表超時
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
複製程式碼
   // 返回false,代表超時。返回true,代表獲得共享鎖成功
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }
複製程式碼

如果在nanosTimeout時間範圍內,還沒有獲取共享鎖成功的話,直接返回false。spinForTimeoutThreadshold的值為1000nanoseconds。如果shouldParkAfterFailedAcquire(p, node)返回true且超時時間大於閥值spinForTimeoutThreadshold的話,會通過LockSupport.parkNanos(this, nanosTimeout);讓執行緒掛起nanosTimeout時間。這樣的策略體現是:如果超時時間很短的話,就不把當前執行緒掛起,而是通過自旋,這樣執行緒獲取鎖很快就釋放的情況下,可以減少cpu資源和執行緒掛起和恢復的效能損耗。

    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        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 true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
複製程式碼

總結

通過重寫AQS中的模板方法,可以實現共享鎖和獨佔鎖。

如果獲取共享鎖失敗,請求共享鎖的執行緒封裝成SHARED型別的Node物件並且加入到AQS同步佇列中,並掛起Node物件對應的執行緒,等待鎖的釋放。待共享鎖的可以被獲取後,從head節點開始依次喚醒head節點之後的所有SHARED型別的節點,實現共享狀態的傳播。而獨佔鎖則是,當鎖被頭節點獲取後,只有頭節點獲得了鎖,其他節點的執行緒繼續沉睡。等待鎖被釋放了,才會喚醒下一個節點的執行緒,少了setHeadAndPropagate()這一步。

尾言

大家好,我是cmazxiaoma(寓意是沉夢昂志的小馬),希望和你們一起成長進步,感謝各位閱讀本文章。

如果您對這篇文章有什麼意見或者錯誤需要改進的地方,歡迎與我討論。 如果您覺得還不錯的話,希望你們可以點個贊。 希望我的文章對你能有所幫助。 有什麼意見、見解或疑惑,歡迎留言討論。

最後送上:心之所向,素履以往。生如逆旅,一葦以航。

saoqi.png

相關文章