Java 多執行緒基礎 - CyclicBarrier

Yuicon發表於2018-08-12

我的部落格 轉載請註明原創出處。

java.util.concurrent包裡有幾個能幫助人們管理相互合作的執行緒集的類,為多執行緒常見的應用場景預置了抽象好的類庫。在遇到這些應用場景時應該直接重用合適的庫類而不要試圖提供手工的鎖與條件的集合。

Java 多執行緒基礎 - CyclicBarrier

同步屏障 CyclicBarrier

官方定義上文已經給出,人話版是等待特定數量的執行緒都到達同步屏障後各執行緒才繼續執行。

Java 多執行緒基礎 - CyclicBarrier

同步屏障有兩個建構函式,第一個建構函式只需要指定需要等待的執行緒數量,第二建構函式多了一個在特定數量的執行緒都到達同步屏障時優先執行的Runnable

例子:

public class CyclicBarrierTest {

    // 等待4個執行緒到達同步屏障,全部到達後優先執行一個 Runnable
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4,
    () -> System.out.println("全部到達同步屏障" + LocalDateTime.now()));

    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        Runnable runnable = () -> {
            System.out.println("到達同步屏障" + LocalDateTime.now());
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("繼續執行");
        };
        List<Runnable> list = Arrays.asList(runnable, runnable, runnable);
        list.forEach(runnable1 -> new Thread(runnable1).start());
        Thread.sleep(1000);
        System.out.println("最後一個執行緒到達同步屏障");
        cyclicBarrier.await();
    }

}

輸出:

到達同步屏障2018-08-12T14:33:16.769
到達同步屏障2018-08-12T14:33:16.769
到達同步屏障2018-08-12T14:33:16.769
最後一個執行緒到達同步屏障
全部到達同步屏障2018-08-12T14:33:17.746
繼續執行
繼續執行
繼續執行

Process finished with exit code 0

複製程式碼

同步屏障的應用場景是那種多執行緒執行任務,在全部任務執行完成後需要進行一些操作的場景。比如對每個使用者進行充值統計,最後彙總返回。

Java 多執行緒基礎 - CyclicBarrier

CyclicBarrier的方法如上,分別是

getParties()  返回需要到達同步屏障的執行緒數量
await() 等待所有執行緒到達
await(long, TimeUnit) 帶時間限制的await()
isBroken() 判斷阻塞的執行緒是否被中斷
reset() 重置計數器
getNumberWaiting() 當前被阻塞的執行緒數量,該方法主要用於除錯和斷言
複製程式碼

原始碼分析

那麼CyclicBarrier是怎麼實現這個效果的呢?我們從最常用的await()方法入手。

Java 多執行緒基礎 - CyclicBarrier

可以看到await()方法主要是呼叫了CyclicBarrier私有的dowait()方法

Java 多執行緒基礎 - CyclicBarrier

如註釋所言,dowait()方法就是實現功能的主要方法了。

Java 多執行緒基礎 - CyclicBarrier

首先拿到可重入的鎖

Java 多執行緒基礎 - CyclicBarrier
Java 多執行緒基礎 - CyclicBarrier

Java 多執行緒基礎 - CyclicBarrier

然後通過內部類Generation判斷阻塞的執行緒是否被中斷或該屏障已經失效。

Java 多執行緒基礎 - CyclicBarrier

Java 多執行緒基礎 - CyclicBarrier

如果執行緒沒有被中斷,那麼就獲取還沒有到達的執行緒數量並減一。如果已經沒有需要等待的執行緒了,就判斷是否有需要執行的Runnable。如果沒報錯就更新屏障狀態並喚醒所有執行緒繼續執行。Runnable執行報錯的話執行breakBarrier()方法。

Java 多執行緒基礎 - CyclicBarrier

如果還有未到達的執行緒,就進入一個死迴圈,直到超時、執行緒中斷、屏障失效、全部完成等情況下退出。

完整的程式碼:

/**
     * Each use of the barrier is represented as a generation instance.
     * The generation changes whenever the barrier is tripped, or
     * is reset. There can be many generations associated with threads
     * using the barrier - due to the non-deterministic way the lock
     * may be allocated to waiting threads - but only one of these
     * can be active at a time (the one to which {@code count} applies)
     * and all the rest are either broken or tripped.
     * There need not be an active generation if there has been a break
     * but no subsequent reset.
     */
    private static class Generation {
        boolean broken = false;
    }

    /** The lock for guarding barrier entry */
    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();

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     */
    private int count;

    /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     */
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

    /**
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

    /**
     * Main barrier code, covering the various policies.
     */
    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();
            }

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

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

相關文章