闭锁和栅栏的区分以及适用场景

开篇

相信小伙伴对这个两个词或多或少都有些了解,他们是在并发编程中经常使用的线程通信工具。二者十分类似,可是又有不一样,致使不少小伙伴也包括我在内产生了不少困惑:他们两个究竟有什么区别,以及适用于什么场景呢?
下面听我缓缓道来,不想看例子或者过程的小伙伴能够拉到最下面看总结呦spring

闭锁

闭锁(CountDownLatch)坊间俗称计数器,官方(谷歌机翻,哈哈)解释:编程

/**
 * A synchronization aid that allows one or more threads to wait until
 * a set of operations being performed in other threads completes.
 */

容许一个或多个线程等待,直到在其余线程中执行的一组操做完成的同步辅助程序。

大概意思就是说,能够有一个或者多个线程,等待其余线程都完成某个操做后,再继续执行。
什么意思呢?举个栗子吧:
生活中应该常常碰见一种状况,坐公交车是,尤为是始发站,司机师傅每每为了一次拉更多的乘客,会等到车上乘客的数量到达必定程度之后才会发车。测试代码以下:segmentfault

public static void main(String[] args) {
        List<Passenger> list = new ArrayList<>();
        Passenger p1 = new Passenger("看会书");
        Passenger p2 = new Passenger("看会手机");
        Passenger p3 = new Passenger("看会风景");
        Passenger p4 = new Passenger("看会售票员");
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 200, 1000, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), new ThreadFactory() {
            private ThreadGroup group = (null == System.getSecurityManager() ? Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup());
            private AtomicInteger num = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(group, r,"zoo" + num.getAndIncrement(),0);
                thread.setDaemon(false);
                return thread;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy());
        //设定闭锁释放阈值
        CountDownLatch countDownLatch = new CountDownLatch(list.size());
        log.error("司机师傅人够一车再发车,等会人吧...");
        for (Passenger p : list) {
            executor.execute(()->gotoZOO(p,countDownLatch));
        }
        try {
            countDownLatch.await();
            log.error("人够了,起飞!");
            executor.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private static void  gotoZOO(Passenger p,CountDownLatch countDownLatch){
        log.error("{}的乘客上车啦",p.getDoWhat());
        try {
            countDownLatch.countDown();
            log.error("{}",p.doWhatOnBus());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class Passenger{
        private String doWhat;

        public Passenger(String doWhat) {
            this.doWhat = doWhat;
        }

        public String getDoWhat() {
            return doWhat;
        }

        public String doWhatOnBus() {
            return "车上好无聊啊,"+doWhat+"吧!";
        }
    }

执行结果微信

23:46:34.698 [main] ERROR com.test - 司机师傅人够一车再发车,等会人吧...
23:46:34.757 [zoo1] ERROR com.test - 看会手机的乘客上车啦
23:46:34.758 [zoo3] ERROR com.test - 看会售票员的乘客上车啦
23:46:34.757 [zoo0] ERROR com.test - 看会书的乘客上车啦
23:46:34.759 [zoo1] ERROR com.test - 车上好无聊啊,看会手机吧!
23:46:34.759 [zoo3] ERROR com.test - 车上好无聊啊,看会售票员吧!
23:46:34.757 [zoo2] ERROR com.test - 看会风景的乘客上车啦
23:46:34.759 [zoo0] ERROR com.test - 车上好无聊啊,看会书吧!
23:46:34.759 [zoo2] ERROR com.test - 车上好无聊啊,看会风景吧!
23:46:34.759 [main] ERROR com.test - 人够了,起飞!

司机师傅(主线程)要等上了4个乘客之后才发车(等待4个子线程完成完成某件事之后调用countDown方法),而乘客上车(调用countDown)之后该作本身的事还作本身的事情,不会由于上了车就傻呆呆的什么都不干了(不会由于调用了countDown而阻塞自身)。等司机师傅看人够了(到达设定阈值),就发车了。多线程

闭锁总结:
主线程调用await后会阻塞等待其余子线程调用countDown方法将设定阈值减至0,而后在继续执行。
而子线程不会由于调用了countDown方法而阻塞

栅栏

栅栏(CyclicBarrier)官方解释:并发

/**
 * A synchronization aid that allows a set of threads to all wait for
 * each other to reach a common barrier point.  CyclicBarriers are
 * useful in programs involving a fixed sized party of threads that
 * must occasionally wait for each other. The barrier is called
 * <em>cyclic</em> because it can be re-used after the waiting threads
 * are released.
 */
同步帮助,容许一组线程互相等待,以达到共同的障碍点。 CyclicBarriers在涉及固定大小的线程方的程序中颇有用,这些线程有时必须互相等待。该屏障称为<em> cyclic </ em>,由于它能够在释放等待线程后从新使用。

从类注释上咱们能够大体了解到,他是运用在一组,也便是多个线程中的,当全部线程到达某个状态前一直阻塞,直到全部线程都达到后再继续执行。并且是能够重复使用的。ide

上面的描述仍是太晦涩了,仍是举个栗子:
咱们小时候学校都组织过春游,规定好地点,等人到齐了就一块儿进去玩。写了个简单的例子,看这种场景栅栏是怎么工做的工具

public static void main(String[] args) {
        List<Boy> list = new ArrayList<>();
        Boy boy1 = new Boy("看老虎");
        Boy boy2 = new Boy("看猩猩");
        Boy boy3 = new Boy("看狮子");
        Boy boy4 = new Boy("看售票员");
        list.add(boy1);
        list.add(boy2);
        list.add(boy3);
        list.add(boy4);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 200, 1000, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), new ThreadFactory() {
            private ThreadGroup group = (null == System.getSecurityManager() ? Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup());
            private AtomicInteger num = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(group, r,"zoo" + num.getAndIncrement(),0);
                thread.setDaemon(false);
                return thread;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化栅栏,设置障碍点阈值
        CyclicBarrier cyclicBarrier = new CyclicBarrier(list.size());
        for (Boy boy : list) {
            executor.execute(()->gotoZOO(boy,cyclicBarrier));
        }
    }

    private static void  gotoZOO(Boy boy,CyclicBarrier cyclicBarrier){
        log.error("人还没到齐呢,等一下吧,{}的小男孩开始等待",boy.getWhere());
        try {
            cyclicBarrier.await();
            log.error("{}",boy.goWhere());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class Boy{
        private String where;
        public Boy(String where) {
            this.where = where;
        }

        public String getWhere() {
            return where;
        }

        public String goWhere() {
            return "人到齐了,我要去"+where+"啦!";
        }
    }

执行结果:测试

22:05:59.476 [zoo2] ERROR com.test - 人还没到齐呢,等一下吧,看狮子的小男孩开始等待
22:05:59.477 [zoo1] ERROR com.test - 人还没到齐呢,等一下吧,看猩猩的小男孩开始等待
22:05:59.477 [zoo0] ERROR com.test - 人还没到齐呢,等一下吧,看老虎的小男孩开始等待
22:05:59.476 [zoo3] ERROR com.test - 人还没到齐呢,等一下吧,看售票员的小男孩开始等待
22:05:59.484 [zoo0] ERROR com.test - 人到齐了,我要去看老虎啦!
22:05:59.484 [zoo2] ERROR com.test - 人到齐了,我要去看狮子啦!
22:05:59.484 [zoo3] ERROR com.test - 人到齐了,我要去看售票员啦!
22:05:59.484 [zoo1] ERROR com.test - 人到齐了,我要去看猩猩啦!

咱们能够发现前三个小男孩在到达之后都没有进到动物园里,而是直到第四个小男孩来到之后,四个小男孩才进入动物园,在此以前每来一个小朋友就多一个小朋友等待(每一个线程调用await方法),直到等待全部人到齐(线程阻塞等待达到栅栏障碍点4),各个小男孩再去继续进入动物园看动物(各线程继续执行本身的任务)。就像是动物园大门的栅栏,买的是团体票,每次必须人到齐才放开让小朋友进去同样。this

栅栏总结
各子线程相互等待,直到达到栅栏初始化时的阈值,则继续执行

区分以及我的理解

闭锁:有点相似于一个统计功能(可能这也是为何他俗称计数器),主线程调用await方法阻塞等待统计结果,而子线程只负责在达到统计要求时调用countDown方法告诉主线程我好了,而不会阻塞自己;有一个负责接收结果(主线程)和一个或多个发送数量的(子线程);
栅栏:首先在线程调用await方法时会阻塞当前线程,其次我的理解他没有相似像闭锁那样的主子的关系,他是各个线程相互等待,都到达某个点的时候,则继续执行。

适用场景

其实从上面的区分就能看出一些:若是是须要将多线程执行完成与否的接口汇总到某一个线程中,而后再继续执行的状况,好比每条线程计算一个指标,都计算完成之后再计算全部指标的总和或者其余的,就可使用闭锁;
而若是只是各个线程须要等各个线程都完成了,再继续本身的事,可使用栅栏,好比ABC三个线程分别去获取123三个指标,而后再A要取这三个数的平均数,B要取总和,C要取方差,那就须要等ABC都先取完了123这三个指标,才能计算,这时候就能够用到栅栏了。

总结

这两种都是很是好的线程通信工具,不过细节仍是有所差别。
总得来讲就是:

闭锁是为了在 某一条线程等待获取到 其余线程的执行结果;
而栅栏则是线程间的 相互等待,而后再同时开始作 各自的事情

最后

文中的代码只是为了比较好的说明两种工具的差别,写的很差还请小伙伴们多多包涵,若是发现有哪点写的不对的也欢迎你们伙们留言,咱们共同进步!最后若是小伙伴以为文章不错,不妨动动小手点个赞再走,不要下次必定呦~


最后的最后

给走过路过的小伙伴们推荐一个公众号,”菜鸟封神记“,里面干活满满,最新在更新spring源码相关的文章,感兴趣的小伙伴不妨关注一下,微信搜索公众号:”菜鸟封神记“,或者扫描下面公众号关注便可。2223.png

相关文章
相关标签/搜索