查看原文
其他

【并发技术15】线程同步工具CyclicBarrier的使用

倪升武 武哥聊编程 2022-08-24


文末给大家推荐个很有尿性的公众号。


我们知道,Semaphore 同步工具主要提供了一个记数信号量,允许最大线程数运行。CyclicBarrier 是另一个同步工具,本文主要来总结一下 CyclicBarrier 的使用。先看一下官方的对 CyclicBarrier 的介绍:

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

我的解释是这样的:

CyclicBarrier 可以使不同的线程彼此等待,在不同的线程都执行完了后,再执行下面的程序。比如A、B、C三个同学要去玩,大巴在校门口,A、B、C分别从各自的寝室出来,先后到达大巴处,先来的必须等待,直到三个同学都来了,大巴才能走。他们要玩两个地方M和N,到了M处,三个同学又各自去玩了,玩完后各自回到大巴上,先回来的必须等待,直到三个同学都到了,大巴才能到N处,这个大巴可以循环利用。这个大巴就是 CyclicBarrier。

这 CyclicBarrier 同步工具相对来说比较简单,因为功能很明确,下面写一个 CyclicBarrier 的示例代码:

  1. public class CyclicBarrierTest {

  2.    public static void main(String[] args) {

  3.        ExecutorService service = Executors.newCachedThreadPool();

  4.        final CyclicBarrier cb = new CyclicBarrier(3); //设置要三个线程等待,都执行完了再往下执行

  5.        System.out.println("初始化:当前有" + (cb.getNumberWaiting() + "个线程在等待"));

  6.         //3个任务

  7.        for (int i = 0; i < 3; i++) {

  8.            Runnable run = new Runnable() {  

  9.                public void run() {  

  10.                    try {  

  11.                        Thread.sleep((long)(Math.random()*10000));  

  12.                        System.out.println(Thread.currentThread().getName()

  13.                                + "即将到达集合点1,当前已有" + (cb.getNumberWaiting()+1) + "个线程到达,"

  14.                                + (cb.getNumberWaiting()==2?"都到齐了,去集合点2!":"正在等候……"));  

  15.                        // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印3条记录,之后线程一直阻塞

  16.                        cb.await(); //等待

  17.                        Thread.sleep((long)(Math.random()*10000));  

  18.                        System.out.println(Thread.currentThread().getName()

  19.                                + "即将到达集合点2,当前已有" + (cb.getNumberWaiting()+1) + "个线程到达,"

  20.                                + (cb.getNumberWaiting()==2?"都到齐了,去集合点3!":"正在等候……"));  

  21.                        cb.await();

  22.                        Thread.sleep((long)(Math.random()*10000));  

  23.                        System.out.println(Thread.currentThread().getName()

  24.                                + "即将到达集合点3,当前已有" + (cb.getNumberWaiting()+1) + "个线程到达,"

  25.                                + (cb.getNumberWaiting()==2?"都到齐了,执行完毕!":"正在等候……"));  

  26.                        cb.await();

  27.                    } catch (Exception e) {  

  28.                    }  

  29.                }  

  30.            };  

  31.            service.execute(run);  //执行任务

  32.        }  

  33.        service.shutdown(); //关闭线程

  34.    }

  35. }


从代码中可以看出,CyclicBarrier 的使用主要有两点,一是初始化,二是调用 await() 方法。这个 await() 方法也就是官方解释中的“公共屏障点”,到了这个点,所有线程都得等待,直到规定数量的线程全部到达才能往下执行。看一下运行效果:

初始化:当前有0个线程在等待
pool-1-thread-3即将到达集合点1,当前已有1个线程到达,正在等候……
pool-1-thread-2即将到达集合点1,当前已有2个线程到达,正在等候……
pool-1-thread-1即将到达集合点1,当前已有3个线程到达,都到齐了,去集合点2!
pool-1-thread-2即将到达集合点2,当前已有1个线程到达,正在等候……
pool-1-thread-3即将到达集合点2,当前已有2个线程到达,正在等候……
pool-1-thread-1即将到达集合点2,当前已有3个线程到达,都到齐了,去集合点3!
pool-1-thread-3即将到达集合点3,当前已有1个线程到达,正在等候……
pool-1-thread-1即将到达集合点3,当前已有2个线程到达,正在等候……
pool-1-thread-2即将到达集合点3,当前已有3个线程到达,都到齐了,执行完毕!

CyclicBarrier 的应用场合也很明显:在某种需求中,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择CyclicBarrier 了。 

可以再确切一点:假如我们需要统计全国的业务数据,其中各省的数据库是独立的,也就是说按省分库。并且统计的数据量很大,统计过程也比较慢。为了提高性能,快速计算。我们采取并发的方式,多个线程同时计算各省数据,最后再汇总统计,在这里 CyclicBarrier 就非常有用。

CyclicBarrier 就聊这么多吧。这里有技术、有段子、有生活、有资源,来吧,还等什么呢~


最后给大家推荐一个很有尿性的公众号:『说点特别的』,作者老高,为人真诚,Database、Linux、Aix、PM 及各类技术爱好者。每日O点准时推文,分享一些自己的工作、生活、学习心得。白天谋生存,晚上谋发展。一名懂业务的技术人,他的世界不只有coding。认真读他文章的人,都知道他基本每天都在原创,周六日都没断更过,这点也是值得我学习的地方。来吧,还等什么呢~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存