CyclicBarrier

CyclicBarrier

字面意思“循环屏障”,翻译为“可重用的屏障”更贴切一点。这个工具类的作用是起到一个栅栏的作用,当指定数量的线程都执行到某一个步骤之后,再一起往下执行,相当于一个阀门,容量满了再开阀门。

看一下这个类的结构:

CyclicBarrier-Outline

这个类没有继承 AbstractQueuedSynchronizer,内部类也没有继承,而是使用了 ReentrantLock lock 这样一个变量来控制线程同步,内部类 Generation 只有一个参数 broken,默认值为 false,用来控制屏障的循环使用。

private static class Generation {
    boolean broken = false;
}

参数

下面来看 CyclicBarrier 的相关参数:

// 控制屏障的锁
private final ReentrantLock lock = new ReentrantLock();
// 控制“突破”屏障的条件,突破屏障后执行 trip.signAll()
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) {
    this(parties, null); // 调用了另一个构造方法
}

// 对没有初始化的参数赋值
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException(); // 参数检验
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

构造方法就是对没有进行初始化的参数进行了赋值。

await

核心方法为 await 方法,来看代码:

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L); // 不限时退出
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
    
public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
    return dowait(true, unit.toNanos(timeout)); // 有限时
}

dowait

await 方法中调用了 dowait 方法,这个才是真正的控制屏障的核心方法:

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 先加锁
    try {
        final Generation g = generation; // 获取当前的分代信息

        // 当屏障已经被“突破”了,抛出异常
        // 也就是一个线程在屏障已经“被突破”之后执行 await 方法会抛出异常
        if (g.broken) 
            throw new BrokenBarrierException();
        // 当线程被中断,破坏屏障,放行所有线程,并且抛出中断异常
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

        int index = --count; // 有一个线程执行 await,count - 1
        // 所有等待的线程都已经执行到了这里,执行传进来的 runnable 任务
        if (index == 0) {  // tripped 
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                // 下一代,在这里实现了重用,唤醒当前等待在屏障处的线程,
                // 将 generation 改为一个新的 Generation,
                // count 重新设置为 parties
                nextGeneration();
                return 0; // 返回 0 表示不需要线程来一起“突破”屏障
            } finally {
                // 如果传进来的 runnable 任务执行出错的话,破坏屏障
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 循环直到“突破”屏障、抛出异常、中断或者时间超时
        for (;;) {
            try {
                if (!timed) // 如果不设置超时,等待 trip 执行 signAll
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    Thread.currentThread().interrupt();
                }
            }
            // 当前线程被唤醒之后,如果 generation 的 broken 为 true,抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
            // 如果 generation 已经是新的了,返回还需要多少个线程才能“突破”屏障
            if (g != generation)
                return index;
            // 如果已经超时,破坏屏障并且唤醒所有线程,抛出异常
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally { // 最后释放锁
        lock.unlock();
    }
}

其他的一些方法是获取信息或者辅助性的,这里就不贴出来了。

总结

CyclicBarrier 的作用是阻塞指定数量的线程,直到指定数量的线程执行了 await 方法后才唤醒所有线程,继续往下执行,并且可以重用。

应用

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {

    private static final int numOfThreads = 5; // 线程数

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(numOfThreads);
        for (int i = 0; i < numOfThreads; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " ---> 已经到了");
                    try {
                        System.out.println(Thread.currentThread().getName() + " ---> 等待所有线程都到");
                        cyclicBarrier.await(); // 等待所有的线程都执行完这一步
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " ---> 突破屏障!");
                }
            }).start();
        }
    }

}

输出结果为:

Thread-0 ---> 已经到了
Thread-3 ---> 已经到了
Thread-4 ---> 已经到了
Thread-4 ---> 等待所有线程都到
Thread-2 ---> 已经到了
Thread-3 ---> 等待所有线程都到
Thread-1 ---> 已经到了
Thread-0 ---> 等待所有线程都到
Thread-1 ---> 等待所有线程都到
Thread-2 ---> 等待所有线程都到
Thread-2 ---> 突破屏障!
Thread-3 ---> 突破屏障!
Thread-4 ---> 突破屏障!
Thread-1 ---> 突破屏障!
Thread-0 ---> 突破屏障!

CountDownLatch 对比

  • CountDownLatch 使用了内部类 Sync 进行控制线程,CyclicBarrier 使用了 ReentrantLockCondition 来控制
  • CyclicBarrier 可以重用,并且只需要调用 awaitCountDownLatch 需要一个或多个线程执行 await,被等待的线程执行 countDown
  • CyclicBarrier 是控制一组线程,而 CountDownLatch 是一组线程等待另一组线程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容