zoukankan      html  css  js  c++  java
  • CyclicBarrier和CountDownLatch的使用

    CyclicBarrier:

    api对CyclicBarrier的描述: 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。  也就是说他可以使一组线程先等待 然后达到某个条件之后再一起执行,有点map/reduce的感觉。

    举个例子: 目前有个int,  分配3个任务线程对他加1 , 最后主任务线程汇集计算结果,代码如下:

        private static AtomicInteger i = new AtomicInteger(0);
        public static void main(String[] args){
            CyclicBarrier cb = new CyclicBarrier(3,new Runnable() {
                //主任务汇集计算结果
                public void run() {
                    System.out.println("结果为" + i.get());
                }
            });
            ExecutorService es = Executors.newFixedThreadPool(5);
            es.submit(new SubTask(cb, "线程一"));
            es.submit(new SubTask(cb, "线程二"));
            es.submit(new SubTask(cb, "线程三"));
            es.shutdown();
        }
        
        //子任务计算
        private static class SubTask implements Runnable{
            private CyclicBarrier cb;
            private String msg;
            public SubTask(CyclicBarrier cb, String msg){
                this.cb = cb;
                this.msg = msg;
            }
            public void run() {
                try {
                    System.out.println(msg + " enter");
                    i.incrementAndGet();
                    Thread.sleep(1000l);
                    cb.await();
                    System.out.println(msg + " quit");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

    结果:

    线程一 enter
    线程三 enter
    线程二 enter
    结果为3

    线程三 quit
    线程二 quit
    线程一 quit

    如果定义的参与者线程比实际的线程要少会怎么样? 比如上例中es提交4个任务结果会怎样?

    api中描述:因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier 所以如果修改代码如下:

            CyclicBarrier cb = new CyclicBarrier(3,new Runnable() {
                //主任务汇集计算结果
                public void run() {
                    System.out.println("结果为" + i.get());
                }
            });
            ExecutorService es = Executors.newFixedThreadPool(5);
            es.submit(new SubTask(cb, "线程一"));
            es.submit(new SubTask(cb, "线程二"));
            es.submit(new SubTask(cb, "线程三"));
            es.submit(new SubTask(cb, "线程四"));
            es.shutdown();                

    则结果是运行完先进入的三个线程之后 第四个线程一直堵塞。

    可能的输出:

    线程一 enter
    线程三 enter
    线程二 enter
    线程四 enter
    结果4
    线程三 quit
    线程四 quit
    线程一 quit

    如上结果所示  有一个线程一直堵塞中

    CountDownLatch:

    api对CountDownLatch描述:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier

    由此可见: CountDownLatch与CyclicBarrier的区别是: CyclicBarrier可以重复使用  而CountDownLatch不能重复使用

    简单例子如下:

        public static void main(String[] args) throws InterruptedException, BrokenBarrierException{
            //分配3个子任务去完成
            CountDownLatch cdl = new CountDownLatch(3);
            Thread t = new Thread(new SubTask(cdl, "线程1"));
            Thread t1 = new Thread(new SubTask(cdl, "线程2"));
            Thread t2 = new Thread(new SubTask(cdl, "线程3"));
            t.start();
            t1.start();
            t2.start();
            //在3个子任务完成之前一直等待
            cdl.await();
            //3个子任务完成之后  主线程获取结果
            System.out.print(i.get());
        }
        
        //子任务计算
        private static class SubTask implements Runnable{
            private CountDownLatch cb;
            private String msg;
            public SubTask(CountDownLatch cb, String msg){
                this.cb = cb;
                this.msg = msg;
            }
            public void run() {
                i.incrementAndGet();
                System.out.println(msg + "进入");
                try {
                    Thread.sleep(1000l);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                cb.countDown();
            }
        }

    结果:

    线程2进入
    线程1进入
    线程3进入
    3

    还有一个与以上两个类使用方式非常相似的类: Semaphore

        public int MAX = 10;
        public Semaphore s = new Semaphore(MAX);
    
        public void semaphoreTest() throws InterruptedException{
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        s.acquire(MAX);
                        for(int i = 0; i<MAX; i++){
                            s.release();
                            Thread.sleep(1000L);
                        }
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            
            Thread.sleep(1000L);
            
            while(true){
                try{
                    s.acquire();
                    System.out.print(1);
                }catch(Exception e){
                    break;
                }
            }
        }

    这个类最大的作用个人感觉就是把许可数设置为1: new Semaphore(1) 然后充当一个互斥锁。这种方式与lock比较的优势在于: lock只能有持有他的线程来释放,而semaphore实现的互斥锁可由任何线程释放,对死锁恢复非常有帮助

  • 相关阅读:
    python Flask基础使用
    安装docker以及常规操作
    关于InfiniBand几个基本知识点解释
    RDMA技术解析
    C++学习之 类
    C++学习 内存模型和名称空间
    C++基础知识(3)
    C++基础知识(2)
    C++基础知识(1)
    Java基础知识
  • 原文地址:https://www.cnblogs.com/hithlb/p/4286659.html
Copyright © 2011-2022 走看看