1. 程式人生 > >併發程式設計(5)——AQS之CountDownLatch、Semaphore、CyclicBarrier

併發程式設計(5)——AQS之CountDownLatch、Semaphore、CyclicBarrier

CountDownLatch

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

通常情況下,countDown如下呼叫

CountDownLatch countDownLatch = new CountDownLatch(1);
countDownLatch.countDown();
countDownLatch.await();

看一下countDown方法:

public void countDown() {
        sync.releaseShared(1);
    }

AQS中releaseShared方法如下:

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

CountDownLatch中tryReleaseShared方法如下:

// 方法判斷許可如果減1之後是否為0,如果為0的話就執行doReleaseShared()方法。
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;
            }
        }

來看doReleaseShared()方法:

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;
        }
    }

不過尷尬的是,CountDownLatch這裡未做任何事情。

再看一下await()方法:

await方法會讓當前執行緒進入wait狀態,除非滿足下面兩個條件:

  1. count到0
  2. 執行緒中斷
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

tryAcquireShared方法如下:

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

所以,當state不是0的時候進入doAcquireSharedInterruptibly方法。

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) {
                    // 只有當state為0時r為1
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 如果state不為0,該執行緒會進入wait狀態
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

CountDownLatch文件中有一句非常重要的話:
Memory consistency effects: Until the count reaches zero, actions in a thread prior to calling countDown() happen-before actions following a successful return from a corresponding await() in another thread
大意是一個執行緒countdown()之前的操作happens-before另一個執行緒中await()之後的操作。

Semaphore

Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource.
Semaphore主要用來限制獲取資源的執行緒數。
Actions in a thread prior to calling a "release" method such as release() happen-before actions following a successful "acquire" method such as acquire() in another thread
記憶體語義:release() happen-before acquire()之前
啟一個springboot專案,寫一個方法:

@RequestMapping("/test/semaphore")
    @ResponseBody
    public void test() throws InterruptedException {
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 7; i++) {
            int finalI = i;
            new Thread(()->{
                try {

                    semaphore.acquire();
                    System.err.println(Thread.currentThread() + "獲取了許可" + semaphore.availablePermits());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "執行緒" + i).start();

        }
        new Thread(()->{
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.err.println(Thread.currentThread() + "要釋放許可" + semaphore.availablePermits());
            semaphore.release();
        }, "執行緒7").start();
    }

一次輸出如下:
Thread[執行緒1,5,main]獲取了許可4
Thread[執行緒0,5,main]獲取了許可3
Thread[執行緒3,5,main]獲取了許可2
Thread[執行緒4,5,main]獲取了許可0
Thread[執行緒2,5,main]獲取了許可0
Thread[執行緒7,5,main]要釋放許可0
Thread[執行緒5,5,main]獲取了許可0
會發現,執行緒5獲取許可之前是先等執行緒7釋放許可。
至於執行緒6會因為由於許可為0,進入等待狀態。直到有執行緒釋放許可,來呼叫unparkSuccessor。

CyclicBarrier

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
Actions in a thread prior to calling await() happen-before actions that are part of the barrier action, which in turn happen-before actions following a successful return from the corresponding await() in other threads.

內部類Generation只有一個屬性broken(預設false)
我們發現,await()方法如下:

 public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

進入dowait方法:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            // 來一個執行緒count減1,如果index為0,就會翻車
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 沒翻車(broken,interrupted,timed out)的話就執行下面的邏輯
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

下面進入trip.await()方法

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 往等待佇列加入節點Node
            Node node = addConditionWaiter();
            // 這裡釋放AQS中的state, 如果釋放失敗,會將node的waitstatus置為CANCELLED,這是傳參node的唯一用處
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 如果node有next就肯定返回true
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 如果當前執行緒
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

進入addConditionWaiter()

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

假如5個執行緒按順序進入await(),則此時,trip這個ConditionObject上firstWaiter==lastWaiter==new Node("執行緒0對應的執行緒", Node.CONDITION)

同時,因為dowait方法中的lock.lock(),AQS的同步佇列如下:

head節點--》執行緒1--》執行緒2--》執行緒3--》執行緒4(tail)

等待佇列: t0

當釋放執行緒0的鎖之後,喚醒執行緒1,將執行緒1加入等待佇列,執行緒2/3也加入等待佇列。此時同步佇列還剩下執行緒4。此時佇列情況是:

同步佇列:head節點

等待佇列:t0->t1->t2->t3

到了最後一個執行緒4執行的時候,index==0,執行nextGeneration,會signalAll trip這個Condition上的所有等待執行緒。所以經過signalAll之後,佇列情況變成了:

同步佇列:head->t0->t1->t2->t3

等待佇列:空

此時執行緒4執行,釋放鎖之後喚醒同步佇列上的第一個節