• 并发工具——CyclicBarrier


    本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

    并发编程系列博客传送门

    CyclicBarrier简介

    CyclicBarrier也是JDK并发包中提供的一个辅助并发工具类。CyclicBarrier的作用是让一组线程互相等待,直到这组线程中所有的线程
    都到达同步点(完成某个动作,体现到API上就是调用CyclicBarrier的await方法),这些线程才会继续往下工作。

    在相互等待的线程被释放后,CyclicBarrier可以被循环使用。这个从这个类的名字中的Cyclic就可以看出。

    CyclicBarrier的构造函数

    
    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;
        }
    

    CyclicBarrier提供了两个构造函数。一个构造函数只是指定相互等待的线程个数,另外一个构造函数指定了互相等待的线程个数,还指定了
    栅栏打开之后会触发的动作。

    CyclicBarrier的典型用法

    下面的代码模拟了一个解析Excel的过程:这个Excel有5个sheet,使用5个线程分别去解析这个5个sheet,等5个线程全部解析完成后打印出解析结果。

    
    public class CyclicBarrierTest {
    
    
        public static void main(String[] args) {
    
            Map<String, Integer> mockExcelSheet = new HashMap<>();
            mockExcelSheet.put("sheet1", 1);
            mockExcelSheet.put("sheet2", 2);
            mockExcelSheet.put("sheet3", 3);
            mockExcelSheet.put("sheet4", 4);
            mockExcelSheet.put("sheet5", 5);
            AtomicInteger result = new AtomicInteger(0);
    
            CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new SummaryClerker(result));
            for (int i = 0; i < 5; i++) {
                new Thread(new Worker(cyclicBarrier,mockExcelSheet,result,i+1)).start();
            }
        }
    
    
        private static class Worker implements Runnable {
    
            private CyclicBarrier cyclicBarrier;
            private Map<String, Integer> mockExcelSheet;
            private AtomicInteger result;
            private int threadIndex;
    
            public Worker(CyclicBarrier cyclicBarrier,
                          Map<String, Integer> mockExcelSheet,
                          AtomicInteger result,
                          int threadIndex) {
                this.cyclicBarrier = cyclicBarrier;
                this.mockExcelSheet = mockExcelSheet;
                this.result = result;
                this.threadIndex = threadIndex;
            }
    
            @Override
            public void run() {
                String sheetName = "sheet" + threadIndex;
                Integer integer = mockExcelSheet.get(sheetName);
                int processData = processData(integer);
                result.addAndGet(processData);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
    
            private int processData(int originalData) {
                return originalData * 10;
            }
        }
    
    
        private static class SummaryClerker implements Runnable {
            private AtomicInteger result;
    
            public SummaryClerker(AtomicInteger result) {
                this.result = result;
            }
    
            @Override
            public void run() {
                System.out.println("the final result is:" + result.toString());
            }
        }
    
    }
    
    

    CountDownLatch的区别

    CountDownLatchCyclicBarrier功能类似。下面举个列子来说明下这两个类的主要区别。

    区别一:强调的重点不一样

    CountDownLatch更加强调一组线程等待另外一组线程。还是那之前的场景举例:

    小王和他老婆分别请了他们的同事来家里吃晚饭,一共请了6个同事。他们会一直等到6个同时全都到齐了才开始吃晚饭。
    这个场景中,小王和他老婆就是线程组A,他们的6个同事就是线程组B,线程组A的线程会一直await,知道线程组B的6个同事全部到达,调用countDown

    CyclicBarrier更加强调一组线程之间的互相等待。

    小王和公司户外俱乐部的同事相约去爬佘山。他们相约在景区入口处集合,等全部的人都到齐后再一起去爬山。这个景区入口就是一个同步点(栅栏),所有
    同事都到齐后这个栅栏会打开。

    clipboard.png

    区别二:可重用性不一样

    CountDownLatch计数归零后就不能重复利用了,但是CyclicBarrier可以reset进行重复利用。

    简单总结

    CyclicBarrier源代码比较简单,这边就不具体分析源代码了。

    总的来说,CyclicBarrier的主要功能是一组线程互相等待,线程全部到达同步点后这些线程才继续往下工作。
    实现的原理是:先到达的线程调用Condition的await方法,进入waiting状态。最后一个到达的线程会调用signalAll方法唤醒所有线程继续往下工作。

    一个小坑
    CyclicBarrier的reset方法有一个小坑需要我们注意下。

    在调用reset之后,只会重置已经到达屏障的线程(已经调用await的线程)因此会存在一个问题(下面还是以爬山的列子说明这个问题):

    两拨人相约去爬山,每波人20个。第一拨人约定上午9点在景区门口集合,第二波人约定10点左右集合。

    第一波人中,19个人在9点左右都到达了景区门口了,但是剩下的一个人因为堵车而没来的及赶到。这19个人就在景区门口一直等这个迟到的同事。突然在等的过程中,有个同事身体不舒服,就提议取消这次活动,所以大家都散了。

    但是堵车的那个同事还没到,也没人通知他活动取消了。他匆匆忙忙终于还是在9点40左右到达了,但是现在他已经分不清自己是第一波人还是第二波人了,第二波人也不知道这个人其实是属于第一波人的。所以只要第二波人中来了19个人他们就开始去爬山了。

    第二波人中最后一个到达的会一直在那等待其他的19个人,但实际上他们已经去爬山了。

    其实这种情况可以给线程设置一个超时时间来解决。在这个场景中的体现就是,每个人只等待30分钟,还没到期就打道回府。

  • 相关阅读:
    微信小程序@bindgetuserinfo @bindgetphonenumber
    报错总结
    前端面试题
    关于vue ssr next服务端渲染
    【012】JavaSE面试题(十二):多线程(2)
    【011】JavaSE面试题(十一):多线程(1)
    [010]
    [009]
    [008]
    添加Lombok插件后调用Setter或Getter方法IDEA编译错误
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/12752749.html
Copyright © 2020-2023  润新知