JUC 之 CyclicBarrier 源码分析

JUC 之 CyclicBarrier 源码分析

上一篇我们介绍了 CountDownLatch 的实现原理,它使用 state 变量来表明等待线程的数量,而本篇的 CyclicBarrier 被称为线程屏障,即让一组线程(也是通过 AQS 的 state 变量来记录线程个数)都到达某个屏障点,然后所有线程一起继续执行。下面我们先来看个使用示例

static class Solver {
        final int N;
        final CyclicBarrier barrier;
        class Worker implements Runnable {
            int myRow;
            Worker(int row) {
                myRow = row;
            }
            public void run() {
                /*while (!done()) {}*/
                processRow(myRow);// 业务处理逻辑
                try {
                    barrier.await();// 业务处理完,等待其它线程处理完,然后同时往下执行
                } catch (Exception ex) {}
                ThreadUtil.printMsg("work done: " + myRow);
            }
        }

        public Solver(int num) {
            N = num;
            Runnable barrierAction = () -> ThreadUtil.printMsg("barrierAction");// 所有线程到达屏障点后回调该任务
            barrier = new CyclicBarrier(N, barrierAction);// 创建 CyclicBarrier 对象
            List<Thread> threads = new ArrayList<>(N);
            for (int i = 0; i < N; i++) {
                Thread thread = new Thread(new Worker(i));
                threads.add(thread);
                thread.start();
            }
            for (Thread thread : threads) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

上述例子,我们让每个线程单独处理各自业务,处理完成后调用 CyclicBarrier 的 await 方法等待其它线程处理完各自任务到达同一屏障点,然后统一唤醒所有阻塞线程往下执行各自剩余业务,然后退出。直到怎么使用后下面我们开始源码学习。

核心成员

通过下述信息我们看到 CyclicBarrier 并不像 CountDownLatch 一样直接操作 AQS 的 state 值,而是通过 ReentrantLock 和 Condition 来实现的。这两个类的源码已在【JUC 之 ReentrantLock 源码解析】和 【AQS 之 Condition 源码剖析】两篇中详细讲解过,详情请查看这两篇的介绍。

// 与 CountDownLatch 不一样的是 CyclicBarrier 可以通过 reset 方法来复用,每次开启新的屏障时都会创建一个新的 Generation 对象,用于表示当前轮次
private static class Generation {
    boolean broken = false;
}
// 用于保护屏障操作
private final ReentrantLock lock = new ReentrantLock();
// 用于等待所有线程都到达屏障点的条件变量
private final Condition trip = lock.newCondition();
// 表示当前轮次需要到达屏障点的线程数
private final int parties;
// 所有线程到达屏障点后的回调任务
private final Runnable barrierCommand;
// 表示当前轮次
private Generation generation = new Generation();
// 用于表示还有多少线程没有到达屏障点
private int count;
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties; // 初始值 = parties
    this.barrierCommand = barrierAction;
}

await 方法

现成到达指定屏障点时调用 该方法阻塞线程等待,具体实现用 dowait 方法实现

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

dowait 方法

1、当 count 值变为 0 时,代表是最后一个到达屏障点的线程,该线程将调用 nextGeneration 方法先唤醒所有等待在条件队列上的线程,然后重置 count 和 Generation(CyclicBarrier可重复使用的原因所在)

2、count != 0时,线程将调用 Condition 的 await 方法阻塞等待,

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException, TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();// 获取锁,因为操作 Condition 条件变量时需要线程现持有锁
    try {
        final Generation g = generation;// 保存当前轮次的 Generation
        if (g.broken)// 当前轮次出现异常了,有线程中途退出或执行异常了导致本轮次无法正常进行
            throw new BrokenBarrierException();
        if (Thread.interrupted()) {// 现成本中断了
            breakBarrier();// 终止本轮次的操作,并抛出中断异常
            throw new InterruptedException();
        }
        int index = --count;// 执行到这里说明该线程执行正常,对 count 减一 操作(线程安全)
        if (index == 0) {  // 等于 0 表明最后一个线程达到了屏障点
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;// 回调 barrierCommand 任务
                if (command != null)
                    command.run();
                ranAction = true; 
                nextGeneration();// 还原 count,创建一个新的 Generation 对象,并唤醒所有等待在屏障点的线程
                return 0;
            } finally {
                if (!ranAction)// barrierCommand 任务执行异常,
                    breakBarrier(); // 终止本轮次的进行,将 Generation 的 broken 设置为 true(其它线程将会检测到该值,然后纷纷退出本轮次的操作),当前线程退出本轮次
            }
        }
        // 走到for(;;)循环说明不是最后一个到达屏障点的线程
        for (;;) {
            try {
                if (!timed)// 是否超时等待
                    trip.await();// 无限期等待,await 原理请查看 【AQS 之 Condition 源码剖析】一篇
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);// 超时等待
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    Thread.currentThread().interrupt();// 设置中断标志位
                }
            }

            if (g.broken) // 检测到 broken 被设置为了 true,有别的现成退出了该轮次
                throw new BrokenBarrierException();

            if (g != generation)// Generation 发生了变更,说明已经处于下一轮次当中,本轮次直接退出即可
                return index;

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

private void nextGeneration() {
    // 唤醒所有等待线程
    trip.signalAll();
    // 重置 count 值
    count = parties;
    generation = new Generation();// 表示进入下一轮次
}

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();// 唤醒所有等待线程,各自线程如果检测到 generation.broken 被设置为了 ture ,然后纷纷抛出 BrokenBarrierException 退出本轮次的操作,进入到下一轮次
}

reset 方法

我们知道 parties 用于记录需要到达屏障点的线程数,也即用户创建 CyclicBarrier 时传入的计数,count 值用于记录还有多少线程未到达屏障点,generation 用与表明屏障属于哪一轮次,那么如果需要重复使用 CyclicBarrier ,调用 reset 方法重置 count 和 generation 让其进入新一轮的屏障。

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // 终止当前轮次
        nextGeneration(); // 开启新的轮次
    } finally {
        lock.unlock();
    }
}

总结:通过上述源码可以看到,只有所有线程都到达屏障点后才属于正常情况,这时会调用 nextGeneration 方法唤醒所有等待线程、复位 count、生成新的 generation。而其他线程可能会因为被中断或超时都会造成本轮次操作的失败,即 generation.broken 被设置为了 true,其它线程检测到该值被设置纷纷抛出 BrokenBarrierException 异常而退出等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值