CyclicBarrier的多种使用姿势和简单源码分析

是什么?

循环屏障,顾名思义像一个屏障那样,可以拦截一定数量的线程,当足够数量多的线程到达屏障后,就会“突破”屏障,所有被拦截的线程会继续往下执行,也类似于一个“集合点”“计数器”,不过它可以被循环利用

怎么用?

例子1:

举一个简单的例子,学校开校运会,所有学生站上赛道后才能开跑

public class School {

    private static CyclicBarrier barrier = new CyclicBarrier(3);

    public static void main(String[] args) {
        // 把这个线程池想像成是赛道场地
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // ABC 三名学生站上了赛道
        executor.execute(new Thread(new Student("A")));
        executor.execute(new Thread(new Student("B")));
        executor.execute(new Thread(new Student("C")));
        // 关闭赛道
        executor.shutdown();
    }

    static class Student implements Runnable {
        private String name;
        public Student(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(name + " Ready");
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(name + " Run!");
        }
    }

}

执行结果:
在这里插入图片描述
可以看到barrier可以想象成一个栅栏,等所有学生就位后,挪开栅栏,学生们才开始下一步起跑。
有一点需要注意的是,我们设置了三个学生才开跑,如果只有两个学生,那么比赛将会一直卡着不开始,例如我把C学生注释掉,执行结果就变成了:
在这里插入图片描述

例子二:

ABC三位同学跑步完毕,下一项是游泳,一样是等三个人同时准备好才能开始游:

public class School {

    private static CyclicBarrier barrier = new CyclicBarrier(3);

    public static void main(String[] args) {
        // 把这个线程池想像成是赛道场地
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // ABC 三名学生站上了赛道参加跑步比赛
        executor.execute(new Thread(new StudentRun("A")));
        executor.execute(new Thread(new StudentRun("B")));
        executor.execute(new Thread(new StudentRun("C")));
        // ABC 三名学生站上了赛道参加游泳比赛
        executor.execute(new Thread(new StudentSwim("A")));
        executor.execute(new Thread(new StudentSwim("B")));
        executor.execute(new Thread(new StudentSwim("C")));
        // 关闭赛道
        executor.shutdown();
    }

    static class StudentRun implements Runnable {
        private String name;
        public StudentRun(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(name + " Ready");
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(name + " Run!");
        }
    }

    static class StudentSwim implements Runnable {
        private String name;
        public StudentSwim(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(name + " Ready");
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(name + " Swim!");
        }
    }
}

这个就是CyclicBarrier的循环复用性,它就像一个设置好的坑位,一个线程的统一起跑线,可以重复被使用

注意⚠️:
Barrier只是一个类似计数器的作用,它只知道三个人参加项目,但是具体什么项目它是一概不管的,例如下面的例子
在这里插入图片描述
AB跑步,A又游泳,barrier识别不出来A重复报名和游泳跑步不能揉杂在一起进行,它傻乎乎的觉得够三个人了,那就比赛开始吧!
在这里插入图片描述
于是就乱套了

例子三:

假如校运会有规定,迟到的最后一个罚款200并且当众道歉,那么我们可以定义barrier的时候同时定义“规定“,代码改成:
public class School {

private static CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "同学道歉,并且缴纳了200罚款!");
    }
});

public static void main(String[] args) {
    // 把这个线程池想像成是赛道场地
    ExecutorService executor = Executors.newFixedThreadPool(3);
    executor.execute(new Thread(new StudentRun()));
    executor.execute(new Thread(new StudentRun()));
    executor.execute(new Thread(new StudentRun()));
    // 关闭赛道
    executor.shutdown();
}

static class StudentRun implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " Ready");
            barrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " Run!");
    }
}

}

在这里插入图片描述

例子4:

如果3号选手确实不来了,摔断腿了或者睡过头了,比赛还是要开始,这个时候要设置一个barrier.await的最大等待时间,比如说等三秒,线程3还不来,那就直接开跑
在这里插入图片描述
执行结果:
在这里插入图片描述
注意这个BrokenBarrierException异常,它会打破屏障,让线程停止等待。

源码

先从没什么好说的new方法,跟很多工具类一样,new只是设置了一些初始化参数,方便稍后计数器做线程数统计和额外方法执行
在这里插入图片描述
那么为什么一个await方法就能干这么多事情,继续看下源码:
在这里插入图片描述

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock; // 防止并发问题,锁当前操作
    lock.lock();
    try {
        final Generation g = generation; // 获取当前代的状态

// 判断是否中断,如果中断就抛出屏障Broken异常,所有线程不受管控继续往下走
        if (g.broken) 
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

// count就是我们设置的屏障拦截线程数
        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 {
// 调用await方法进行线程阻塞
                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();
    }
}

而那个nextGeneration,就是循环屏障的关键,它是进行重置计数器参数的作用

在这里插入图片描述
最后看bewakBarrier
在这里插入图片描述

把当前代设置为打破,并唤醒所有线程,同时重置count屏障计数器

CyclicBarrier与CountDownLatch的区别

(1)CountDownLatch是阻塞主线程进行等待,CyclicBarrier是阻塞子线程
(2)CountDownLatch不可重用,计数器归零就不可以再用了
(3)场景不同,CountDownLatch是等N个子线程都做完什么事后,某个线程再去做一件事,而CyclicBarrier是当N个子线程都做完什么事后,N个子线程再一起做什么事

总结

说实话,实际业务场景中,一次都没用过,所以本人的使用也局限于科普式使用,但是有些小场景,例如多线程采集小批次数据流入,1000个线程采集为一小批,或者10分钟await为一小批,排序,比较大小,然后再进行处理,就可以采用Barrier更优雅处理(起码比定时器更简洁高效精准)
等真的有场景用上了,会持续补充…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值