Java併發—— CountDownLatch與CyclicBarrier

午夜12點發表於2018-09-01

CountDownLatch

CountDownLatch閉鎖相當於一扇門,在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何執行緒能通過,當到達結束狀態時,這扇門會開啟並允許所有的執行緒通過。當閉鎖到達結束狀態後,將不會再改變狀態,門永遠保持開啟狀態

CountDownLatch實現原理

CountDownLatch通過內部類Sync實現方法,sync繼承AQS重寫模板中的方法。sync內部定義:


    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中的sync採用共享模式。CountDownLatch示例:


public class TestHarness {
     
    public static long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(nThreads);
         
        for (int i = 0; i < nThreads; i++) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    try {
                        startGate.await();
                        try {
                            System.out.println(Thread.currentThread().getName() + "開始執行");
                            task.run();
                        } finally {
                            endGate.countDown();
                            System.out.println(Thread.currentThread().getName() + "執行結束");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            t.start();
        }
        long start = System.nanoTime();
        startGate.countDown();
        endGate.await();
        long end = System.nanoTime();
        System.out.println("所有執行緒執行完畢,耗時:" + (end-start));
        return end - start;
    }
         
    public static void main(String[] args) throws InterruptedException {
        System.out.println(timeTasks(10, new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "————————work");
            }
        }));
    }
}
複製程式碼

執行結果:


Thread-0開始執行
Thread-3開始執行
Thread-0————————work
Thread-0執行結束
Thread-1開始執行
Thread-2開始執行
Thread-7開始執行
Thread-7————————work
Thread-7執行結束
Thread-9開始執行
Thread-9————————work
Thread-9執行結束
Thread-8開始執行
Thread-8————————work
Thread-8執行結束
Thread-2————————work
Thread-2執行結束
Thread-6開始執行
Thread-1————————work
Thread-6————————work
Thread-6執行結束
Thread-5開始執行
Thread-5————————work
Thread-5執行結束
Thread-3————————work
Thread-3執行結束
Thread-4開始執行
Thread-1執行結束
Thread-4————————work
Thread-4執行結束
所有執行緒執行完畢,耗時:2794976
2794976
複製程式碼

CyclicBarrier

相對於CountDownLatch是一次性物件,一旦進入終止狀態,就不能被重置,CyclicBarrier可以反覆使用。CyclicBarrier類似於閉鎖,與閉鎖的關鍵區別在於,閉鎖用於等待事件,柵欄用於等待其他執行緒,其作用是讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續執行。

CyclicBarrier實現原理

CyclicBarrier構造方法


    public CyclicBarrier(int parties) {
        this(parties, null);
    }
    
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
複製程式碼

引數parties指柵欄攔截的執行緒數量
引數barrierAction指當這些執行緒都到達柵欄時優先會執行的執行緒

await()方法


    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
    
    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();
            // 若執行緒中斷,斷開CyclicBarrier
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            int index = --count;
            // count為0表明所有執行緒到達柵欄位置
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    // 若初始化時指定了所有執行緒到達柵欄時的任務,執行它 
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 喚醒所有等待執行緒,開始新的generation
                    nextGeneration();
                    return 0;
                } finally {
                    // 若任務執行異常,斷開CyclicBarrier
                    if (!ranAction)
                        breakBarrier();
                }
            }
            // 迴圈所有執行緒到達柵欄或柵欄斷開或執行緒中斷或超時
            for (;;) {
                try {
                    // 一直等待
                    if (!timed)
                        trip.await();
                    // 限時等待    
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    // 若執行緒中斷且柵欄沒有斷開,斷開CyclicBarrier
                    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;
                // 若等待超時,斷開CyclicBarrier
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }
複製程式碼

其主要邏輯:若有執行緒未到達柵欄位置,到達柵欄位置的執行緒一直等待狀態,直至發生以下場景:
①. 所有執行緒都到達柵欄位置
②. 有執行緒被中斷
③. 執行緒等待超時
④. 有執行緒呼叫reset()方法,斷開當前柵欄,將柵欄重置為初始狀態

reset方法:


    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 斷開當前柵欄
            breakBarrier();   // break the current generation
            // 開始新的generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }
複製程式碼

CyclicBarrier示例


public class CyclicBarrierTest {
     
    private static CyclicBarrier cyclicBarrier;
     
    static class CyclicBarrierThread extends Thread{
        public void run() {
            System.out.println("運動員:" + Thread.currentThread().getName() + "到場");
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
     
    public static void main(String[] args){
        cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("運動員全部到齊,比賽開始");
            }
        });
         
        for(int i = 0 ; i < 5 ; i++){
            new CyclicBarrierThread().start();
        }
    }

}
複製程式碼

執行結果:


運動員:Thread-0到場
運動員:Thread-1到場
運動員:Thread-2到場
運動員:Thread-3到場
運動員:Thread-4到場
運動員全部到齊,比賽開始
複製程式碼

CountDownLatch與CyclicBarrier區別

①.CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置。所以CyclicBarrier能處理更為複雜的業務場景。例如,如果計算髮生錯誤,可以重置計數器,並讓執行緒重新執行一次

②.CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得Cyclic-Barrier阻塞的執行緒數量。isBroken()方法用來了解阻塞的執行緒是否被中斷

③.CountDownLatch傾向於一個執行緒等多個執行緒,CyclicBarrier傾向於多個執行緒互相等待

相關文章