高级并发编程系列二十(并发流程控制之CyclicBarrier)

141 阅读7分钟

1.引子

天气不错,外面阳光很好!我们接着并发编程系列的上一篇(并发流程控制之CountDownLatch),通过倒计时门栓,我们模拟运动员赛跑,实现了一等多、多等一的编程模型。这里你还记得CountDownLatch的特点吗?它只能倒数计数使用一次,是一个一次性门栓。

今天我要分享给你的是CyclicBarrier,它是循环栅栏,使用方式与CountDownLatch有点像。不同的是它主要的应用场景,适合于我们生活中拼团、凑团模型,比如电商购物拼团,比如参加旅游公司拼团等。CyclicBarrier另外一个区别于CountDownLatch的地方是,它可以重复循环使用,这也是为什么我们通常把CyclicBarrier叫做循环栅栏,而把CountDownLatch叫做一次性门栓的原因。

话不多说,还是让我们show code吧!

2.案例

2.1.代码演示

案例描述:

  • 通过案例,模拟旅游公司拼团旅游
  • 假设某旅游公司,推出了一套x地xx天 的经济实惠的拼团家庭旅游套餐产品,限定拼团3人(满3人即可成为一团)
  • 因为产品经济实惠,天气还不错,非常受欢迎,一下子来了8个人准备参团旅游
  • 你是这款产品的开发工程师
  • 你该如何实现呢?
/**
 * 线程协作之CyclicBarrier
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/1/31 9:19
 */
public class CyclicBarrierDemo {
​
    /**
     * 旅游公司规定的拼团规模,3人一个团(只要凑足了3个人即可出发旅游)
     */
    public static CyclicBarrier tripCyclicBarrier = new CyclicBarrier(3, new Runnable() {
        @Override
        public void run() {
            System.out.println("-----------------本次参团人员满,可以出发了.H起来吧-----------------");
        }
    });
​
    public static void main(String[] args) {
​
        // 拼团旅游,今天天气不错,来的人很多(来了8个人)
        for(int i = 1; i <= 8; i++){
            new Thread(new TripTask(), "消费者(" + i +")").start();
        }
    }
​
    /**
     * 旅游公司拼团旅游
     */
    static class TripTask implements Runnable{
        @Override
        public void run() {
            // 参团人员名称
            String name = Thread.currentThread().getName();
            System.out.println(name + ".计划报名参团旅游.正前往旅游公司.");
            try{
​
                // 随机休眠,模拟去往旅游公司路上消耗的时间
                Random random = new Random();
                long time = random.nextInt(1000);
                Thread.sleep(time);
                // 等候其他参团人员
                System.out.println(name + ".到达旅游公司.耗时【" + time + "】毫秒.开始等待其他参团人员.");
                tripCyclicBarrier.await();
                // 拼团人员到齐,出发旅游
                System.out.println(name + ".参加的旅游团.人员到齐.开开心心去旅游喽.");
​
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
​
        }
    }
}

执行结果:

消费者(3).计划报名参团旅游.正前往旅游公司.
消费者(4).计划报名参团旅游.正前往旅游公司.
消费者(7).计划报名参团旅游.正前往旅游公司.
消费者(1).计划报名参团旅游.正前往旅游公司.
消费者(8).计划报名参团旅游.正前往旅游公司.
消费者(2).计划报名参团旅游.正前往旅游公司.
消费者(5).计划报名参团旅游.正前往旅游公司.
消费者(6).计划报名参团旅游.正前往旅游公司.
    
消费者(3).到达旅游公司.耗时【107】毫秒.开始等待其他参团人员.
消费者(8).到达旅游公司.耗时【151】毫秒.开始等待其他参团人员.
消费者(1).到达旅游公司.耗时【335】毫秒.开始等待其他参团人员.
-----------------本次参团人员满,可以出发了.H起来吧-----------------
消费者(1).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(3).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(8).参加的旅游团.人员到齐.开开心心去旅游喽.
    
消费者(5).到达旅游公司.耗时【380】毫秒.开始等待其他参团人员.
消费者(4).到达旅游公司.耗时【571】毫秒.开始等待其他参团人员.
消费者(2).到达旅游公司.耗时【708】毫秒.开始等待其他参团人员.
-----------------本次参团人员满,可以出发了.H起来吧-----------------
消费者(2).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(4).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(5).参加的旅游团.人员到齐.开开心心去旅游喽.
    
消费者(6).到达旅游公司.耗时【881】毫秒.开始等待其他参团人员.
消费者(7).到达旅游公司.耗时【911】毫秒.开始等待其他参团人员.

2.2.案例分析

2.2.1.执行结果分析

总的有8个人参团,每团参团人员限定3人,即3人可成一团。分析执行结果,我们看到首先8个人先后前往旅游公司:

消费者(3).计划报名参团旅游.正前往旅游公司.
消费者(4).计划报名参团旅游.正前往旅游公司.
消费者(7).计划报名参团旅游.正前往旅游公司.
消费者(1).计划报名参团旅游.正前往旅游公司.
消费者(8).计划报名参团旅游.正前往旅游公司.
消费者(2).计划报名参团旅游.正前往旅游公司.
消费者(5).计划报名参团旅游.正前往旅游公司.
消费者(6).计划报名参团旅游.正前往旅游公司.

因大家距离旅游公司有远近,有人先到,有人后到,先到的先拼成团。我们看到:消费者(3)、消费者(8)、消费者(1)最先拼成一团,这时候,CyclicBarrier提示拼团人员满,并放行:

消费者(3).到达旅游公司.耗时【107】毫秒.开始等待其他参团人员.
消费者(8).到达旅游公司.耗时【151】毫秒.开始等待其他参团人员.
消费者(1).到达旅游公司.耗时【335】毫秒.开始等待其他参团人员.
    
-----------------本次参团人员满,可以出发了.H起来吧-----------------
消费者(1).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(3).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(8).参加的旅游团.人员到齐.开开心心去旅游喽.

紧接着,消费者(5)、消费者(4)、消费者(2)拼成团,CyclicBarrier再次提示拼团人员满,并放行:

消费者(5).到达旅游公司.耗时【380】毫秒.开始等待其他参团人员.
消费者(4).到达旅游公司.耗时【571】毫秒.开始等待其他参团人员.
消费者(2).到达旅游公司.耗时【708】毫秒.开始等待其他参团人员.
    
-----------------本次参团人员满,可以出发了.H起来吧-----------------
消费者(2).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(4).参加的旅游团.人员到齐.开开心心去旅游喽.
消费者(5).参加的旅游团.人员到齐.开开心心去旅游喽.    

最后,我们看到消费者(6)、消费者(7)到达并拼团,但是因为旅游公司限定3人才可成团,还差1人,因此消费者(6)、消费者(7)只能等待,暂时还不能出发旅游:

消费者(6).到达旅游公司.耗时【881】毫秒.开始等待其他参团人员.
消费者(7).到达旅游公司.耗时【911】毫秒.开始等待其他参团人员.

2.2.2.CyclicBarrier关键知识点

通过CyclicBarrier,我们成功实现了旅游公司3人成团的业务编程模型,公司赚了钱老板很开心,老板给你涨了工资你很开心!这是个皆大欢喜的事情。

现在我们回过头来看,使用CyclicBarrier非常简单,只需要两个步骤:

  • 根据业务模型,定义CyclicBarrier
/**
* 旅游公司规定的拼团规模,3人一个团(只要凑足了3个人即可出发旅游)
*/
public static CyclicBarrier tripCyclicBarrier = new CyclicBarrier(3, new Runnable() {
        @Override
        public void run() {
            System.out.println("-----------------本次参团人员满,可以出发了.H起来吧-----------------");
        }
});
  • 将参与拼团成员,通过await等候其它参团人员
 // 等候其他参团人员
System.out.println(name + ".到达旅游公司.耗时【" + time + "】毫秒.开始等待其他参团人员.");
 tripCyclicBarrier.await();
// 拼团人员到齐,出发旅游
System.out.println(name + ".参加的旅游团.人员到齐.开开心心去旅游喽.");