JUC——CyclicBarrier

45 阅读2分钟

1. 作用和使用场景

CyclicBarrier主要是实现了一个可循环利用的屏障(同步屏障),即参与的所有线程都执行到某个点时,才能执行后续的代码,只要有一个线程还未执行到指定点位,则其他所有线程都需要进入等待;类似于公司组织会议,要求所有参会人必须到场,只要有一个人没到场,则会议无法开始,已到场的所有人都必须等待。

2. 主要方法

/**
* 构造方法
* 
* @param parties 参与线程数
* @param barrierAction 最后一个到达屏障的线程需要执行的动作
**/
public CyclicBarrier(int parties, Runnable barrierAction)

public CyclicBarrier(int parties)

/**
* 带超时时间的等待屏障方法
* @param timeout 超时时间
* @param unit 时间单位
**/
public int await(long timeout, TimeUnit unit)

/**
* 无超时时间的等待屏障方法
**/
public int await()

/**
* 执行等待
* @param timed 是否存在超时
* @param nanos 超时的纳秒数
**/
private int dowait(boolean timed, long nanos)

/**
* 重置屏障
**/
public void reset()


3. 核心原理

CyclicBarrier原理并不复杂,主要是利用ReentrantLock和Condition实现线程之间的消息通知 主要实现方法是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();
        }
        // 每个参与屏障的线程都将计数器-1
        int index = --count;
        // 当计数器归零时,表示所有线程都已到达屏障处,由最后一个到达栅栏的线程执行barrierCommand
        // 唤醒所有参与的线程,并进入下一个栅栏
        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 (;;) 的原因如下:
        主要目的
        处理虚假唤醒
        Condition.await() 方法可能会出现虚假唤醒(spurious wakeup)
        需要循环检查条件是否真正满足,而不是仅依赖一次唤醒
        多重条件检查
        需要反复检查多种情况:
        屏障是否被破坏(g.broken)
        是否进入下一代(g != generation)
        是否超时(nanos <= 0L)
        中断处理后的继续等待
        当捕获到 InterruptedException 时:
        如果屏障未被破坏且仍处于同一代,则调用 breakBarrier() 并抛出异常
        否则设置中断状态并继续等待(通过循环重新进入 await 状态)
        **/
        for (;;) {
            try {
                if (!timed)
                    // condition的await方法会释放lock锁
                    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();
    }
}

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