我的博客 转载请注明原创出处。

java.util.concurrent包里有几个能帮助人们管理相互合作的线程集的类,为多线程常见的应用场景预置了抽象好的类库。在遇到这些应用场景时应该直接重用合适的库类而不要试图提供手工的锁与条件的集合。

同步屏障 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

同步屏障的应用场景是那种多线程执行任务,在全部任务执行完成后需要进行一些操作的场景。比如对每个用户进行充值统计,最后汇总返回。

CyclicBarrier的方法如上,分别是

getParties()  返回需要到达同步屏障的线程数量
await() 等待所有线程到达
await(long, TimeUnit) 带时间限制的await()
isBroken() 判断阻塞的线程是否被中断
reset() 重置计数器
getNumberWaiting() 当前被阻塞的线程数量,该方法主要用于调试和断言

源码分析

那么CyclicBarrier是怎么实现这个效果的呢?我们从最常用的await()方法入手。

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

如注释所言,dowait()方法就是实现功能的主要方法了。

首先拿到可重入的锁


然后通过内部类Generation判断阻塞的线程是否被中断或该屏障已经失效。

如果线程没有被中断,那么就获取还没有到达的线程数量并减一。如果已经没有需要等待的线程了,就判断是否有需要执行的Runnable。如果没报错就更新屏障状态并唤醒所有线程继续执行。Runnable执行报错的话执行breakBarrier()方法。

如果还有未到达的线程,就进入一个死循环,直到超时、线程中断、屏障失效、全部完成等情况下退出。

完整的代码:

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

Yuicon
495 声望24 粉丝