Java 併發工具CountDownLatch和CyclicBarrier 原理解析

[TOC]

Java 併發工具CountDownLatch和CyclicBarrier 原理解析

一,簡介

CountDownLatch 允許一個或者多個線程等待其他線程完成操作。

CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程達到一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續運行。

二,代碼演示

CountDownLatchDemo
public class CountDownLatchDemo {

    public static final CountDownLatch count = new CountDownLatch(10);
    private static int j = 0;

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 10; i++) {

            new Thread(
            ()-> {
                System.out.println("我是"+(++j));
                count.countDown();
                }
            ).start();

        }
        count.await();
        System.out.println("我是總數"+j+"!!!");
    }

}

運行結果:
我是1
我是2
我是3
我是4
我是5
我是6
我是7
我是8
我是9
我是10
我是總數10!!!
CyclicBarrierDemo
public class CyclicBarrierDemo {

    private static final CyclicBarrier c = new CyclicBarrier(6,new Thread(() ->
        System.out.println("我是最後一個")
            ));

    private static AtomicInteger index = new AtomicInteger(1);

    public static void main(String[] args) throws Exception, BrokenBarrierException {
        for (int i = 1; i <= 6; i ++) {
            new Thread(() -> {
                try {
                    c.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("我是:"+(index.getAndIncrement()));
            }) .start();
        }
    }

}

運行結果:
我是最後一個
我是:1
我是:2
我是:3
我是:4
我是:5
我是:6

三,源碼解析

CountDownLatch 源碼

原理:

CountDownLatch 又叫做閉鎖,CountDownLatch 的構造函數接受一個int類型的參數作爲計數器,如果你想等待n個節點完成,那就傳入N;當我們調用CountDownLatch 的countDown方法時,N就會減1,CountDownLatch的await會阻塞當前方法,直到N變成0;由於countDown方法可以用在任何地方,這裏說的N個點,可以是N個線程,也可以是1一個線程裏面的N個步驟。

源碼:

// 構造方法
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
   }

// 內部類 Sync 繼承AQS
private static final class Sync extends AbstractQueuedSynchronizer {

}
countDown 方法
    public void countDown() {
        // 調用了AQS的releaseShared方法
        sync.releaseShared(1);
    }
    // 這是Sync的tryReleaseShared 
    // AQS的releaseShared會調用子類的tryReleaseShared 用來控制count
    // tryReleaseShared 共享式的釋放狀態 具體參考AQS
     protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                // 獲取的其實就是我們構造函數的count
                int c = getState();
                // count == 0 證明整個記錄流程已經完畢了
                if (c == 0)
                    return false;
                // 減1 
                int nextc = c-1;
                if (compareAndSetState(c, nextc)) cas 更新
                    return nextc == 0; // 等於0,返回ture 證明計數結束了,可以去喚醒同步隊列的線程了
                                        // 喚醒是AQS的releaseShared方法
                                        // 結合CountDownLatch的await方法理解整這裏 
            }
        }
await 方法
    public void await() throws InterruptedException {
        // 共享式獲取同步轉態
        sync.acquireSharedInterruptibly(1);
    }

    // 這是Sync的方法
    // await 其實是調用的AQS的acquireSharedInterruptibly 但是aqs會調用子類tryAcquireShared
    // 我們看到值有state等於0 纔會返回true 成功 -1 表示失敗 失敗就要加入同步隊列
    // 所以在countDown方法裏面等於0 爲什麼要去喚醒 ,應爲這裏會進入同步隊列
    protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
    }

通過源碼我們可以發現只有當countDown 這個方法計數遞減完畢,別的線程才能執行,因爲調用await的線程會進入AQS的同步隊列,然後阻塞。

CyclicBarrier 源碼

原理:

CyclicBarrier 默認構造方法是CyclicBarrier (int parties),器參數表示屏障攔截的線程數量,每個線程調用await告訴CyclicBarrier 我已經到達屏障了,然後當前線程被阻塞;CyclicBarrier 海提供了一個高級的構造函數,CyclicBarrier (int parties,Runnable barrierAction),用於在線程到達屏障時,優先執行barrierAction線程,方便處理更復雜的業務邏輯。

源碼:

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

    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();
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;
            // 默認爲false Barrier被Broken 就會爲true
            if (g.broken)
                throw new BrokenBarrierException();
            // 線程被中斷了,標記爲breakBarrier,喚醒所有線程
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            // 計數器減減
            int index = --count;
            // 到達 trip
            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();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            // 一直自旋直到發生:tripped, broken, interrupted, timed out
            for (;;) {
                try {
                    // 帶時間
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                    // 自旋過程中發生中斷
                } catch (InterruptedException ie) {
                    // 等於說明當前被重點的這個線程沒有被broken
                    // 拋異常
                    if (g == generation && ! g.broken) { 
                        breakBarrier();
                        throw ie;
                    } else { // 不等於說明後來的線程已經broken了
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        // 中斷線程 breakBarrier已經沒有意義了
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken) // 屏蔽Broken
                    throw new BrokenBarrierException();
                // 別的線程更新了generation 不屬於當前代
                if (g != generation)
                    return index;

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

    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

我們發現CyclicBarrier是所有線程一起阻塞,直到到達屏障點,然後全部喚醒一起執行。

四,總結

CountDownLatch和CyclicBarrier都可以實現一個線程等待一個或者多個線程到達一個點之後才執行,但是這一個或者多個線程的狀態卻是不一樣的,CountDownLatch是來一個執行一個不會阻塞,直到大家執行完了,在執行調用await方法的線程,CyclicBarrier是來一個阻塞一個,直到大家都阻塞完畢,然後在優先執行構造函數裏面的線程,在喚醒所有阻塞的線程;CountDownLatch的計數器只能執行一次,CyclicBarrier可以執行多次,所以CyclicBarrier可以執行復雜的業務場景。

參考 《Java 併發編程的藝術》

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