zoukankan      html  css  js  c++  java
  • CountDownLatch/CyclicBarrie用法记录

    在jdk1.5中,java提供了很多工具类帮助我们进行并发编程,其中就有CountDownLatch和CyclicBarrie

    1.CountDownLatch的用法


    CountDownLatch 位于 java.util.concurrent 包下,其中最主要的方法就是 两个await方法了, 当我们调用await方法时,当前线程会被挂起,直到count的值为零才继续执行

    public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
    
    public boolean await(long timeout, TimeUnit unit)
            throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }

    我们可以写一个小demo看看CountDownLatch的用法

    public class CountDownLatchDemo {
        static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
        static  CountDownLatch countDownLatch=new CountDownLatch(2);
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                                "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                "当前计数器:"+countDownLatch.getCount());
    
            new Thread(new Task(3000)).start();
            new Thread(new Task(5000)).start();
    
            //等待计数器的值为0
            countDownLatch.await();
    
            System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                                "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                "当前计数器:"+countDownLatch.getCount());
        }
    
        static class Task implements Runnable{
            private Integer time;
            public Task(Integer time) {
                this.time=time;
            }
            @Override
            public void run() {
                System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                                    "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                    "当前计数器:"+countDownLatch.getCount());
                try {
                    //模拟业务执行,休眠一段时间
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行完毕之后把计数器的值减一
                countDownLatch.countDown();
                System.out.println(" 当前线    程:"+Thread.currentThread().getName()+" " +
                                    "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                    "当前计数器:"+countDownLatch.getCount());
                        }
        }
    }

    执行的结果是

    当前线程:main 当前时间:2018-01-22 10:22:10当前计数器:2
     当前线程:Thread-0 当前时间:2018-01-22 10:22:10当前计数器:2
     当前线程:Thread-1 当前时间:2018-01-22 10:22:10当前计数器:2
     当前线程:Thread-0 当前时间:2018-01-22 10:22:13当前计数器:1
     当前线程:Thread-1 当前时间:2018-01-22 10:22:15当前计数器:0
     当前线程:main 当前时间:2018-01-22 10:22:15当前计数器:0
    View Code

    通过CountDownLatch 我们可以用于 某个线程A等到其他线程执行完毕后,它才执行

    2.CyclicBarrier的用法


    CyclicBarrier在功能上可能和CountDownLatch有点类似,都有线程挂起的功能,不过CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

    我可以看一个小例子

    public class CyclicBarrierDemo2 {
        public static void main(String[] args) throws InterruptedException {
            int N=3;
            CyclicBarrier cyclicBarrier=new CyclicBarrier(N);
            for(int i=0;i<N;i++){
                Thread.sleep(i*1000);
                new Thread(new Barrier(cyclicBarrier)).start();
            }
        }
    }
    
    class Barrier implements Runnable{
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
        CyclicBarrier cyclicBarrier;
        public Barrier(CyclicBarrier cyclicBarrier){
            this.cyclicBarrier=cyclicBarrier;
        }
        @Override
        public void run() {
            try {
                System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                        "当前时间:"+simpleDateFormat.format(new Date()));
                //模拟业务
                Thread.sleep(3000);
                //等待其他线程到达await状态
                cyclicBarrier.await();
                System.out.println(" 【阻塞完成】当前线程:"+Thread.currentThread().getName()+" " +
                        "当前时间:"+simpleDateFormat.format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    以上代码运行结果是

    当前线程:Thread-0 当前时间:2018-01-22 10:56:51
     当前线程:Thread-1 当前时间:2018-01-22 10:56:52
     当前线程:Thread-2 当前时间:2018-01-22 10:56:54
     【阻塞完成】当前线程:Thread-2 当前时间:2018-01-22 10:56:57
     【阻塞完成】当前线程:Thread-0 当前时间:2018-01-22 10:56:57
     【阻塞完成】当前线程:Thread-1 当前时间:2018-01-22 10:56:57
    View Code

    可以看到3个线程在运行到 cyclicBarrier.await() 时,线程会处于barrier状态,同时会检查其他线程是否也处于barrier 状态了,如果大家都处于barrier状态了,所有线程一起往下接着运行

    CyclicBarrie还有一个构造方法是

    public CyclicBarrier(int parties, Runnable barrierAction) {}

    作用是当子线程都处于barrier状态了,会任意挑选一个线程来执行这个Runnable线程,我们可以把以上案例改造下

    CyclicBarrier cyclicBarrier=new CyclicBarrier(N, new Runnable() {
                @Override
                public void run() {
                    System.out.println("【主线程执行】当前线程:"+Thread.currentThread().getName()+" " +
                            "当前时间:"+simpleDateFormat.format(new Date()));
                }
            });

    执行的结果就是

    当前线程:Thread-0 当前时间:2018-01-22 11:01:05
     当前线程:Thread-1 当前时间:2018-01-22 11:01:06
     当前线程:Thread-2 当前时间:2018-01-22 11:01:08
    【主线程执行】当前线程:Thread-2 当前时间:2018-01-22 11:01:11
     【阻塞完成】当前线程:Thread-2 当前时间:2018-01-22 11:01:11
     【阻塞完成】当前线程:Thread-0 当前时间:2018-01-22 11:01:11
     【阻塞完成】当前线程:Thread-1 当前时间:2018-01-22 11:01:11
    View Code

    CountDownLatch和CyclicBarrie还有个不同是CyclicBarrie可以重用而CountDownLatch却不可以。

    这个其实也很容易理解,CountDownLatch需要不停的减到零才阻塞,这就相当于是个计数器

    而CyclicBarrie 却像一个开关,每次都处于barrier 开关打开。

    CountDownLatch的内部实现


     先从构造函数开始,CountDownLatch的内部也有一个Sync内部类

    Sync 继承了AQS,可见AQS真是并发包中大爸爸,本次为什么想要了解CountDownLatch的内部实现呢,其实也是想借此了解下AQS中共享锁的实现,顺便再次膜拜下Doug Lea

    先从state开始,可以看到和独占锁一样,AQS同样有个state的变量用于控制node节点能否获取到锁。

    再看下await方法

    再看下acquireSharedInterruptibly方法

    1.前两行会检查下线程是否被打断

    2.尝试着获取共享锁,小于0,表示获取失败,如果失败会将当前线程放在队列中

    这一行就是CountDownLatch的核心,如果CountDownLatch的值大于0,线程会一直阻塞,因为线程在获取共享锁的时候必然会失败。

     doAcquireSharedInterruptibly 这个方法应该是把线程放到队列里了

    一直到这,我还没找到共享锁的核心在哪呢,再看下 setHeadAndPropagate 内部

     

    当线程被唤醒后,会重新尝试获取共享锁,而对于CountDownLatch线程获取共享锁判断依据是state是否为0,而这个时候显然state已经变成了0,因此可以顺利获取共享锁并且依次唤醒AQS队里中后面的节点及对应的线程。

    最后总结下

    在获取时,维护了一个sync队列,每个节点都是一个线程在进行自旋,而依据就是自己是否是首节点的后继并且能够获取资源;
    在释放时,仅仅需要将资源还回去,然后通知一下后继节点并将其唤醒。
    这里需要注意,队列的维护(首节点的更换)是依靠消费者(获取时)来完成的,也就是说在满足了自旋退出的条件时的一刻,这个节点就会被设置成为首节点。
    1. CountDownLatch内部有count计数器
    2.count的计数器为0,线程才能获取锁
    3.否则的的话就会进入到队列中去
    4.一旦减到0,队列的第一个节点就会尝试获取锁
    5.获取成功后会唤醒下一个节点,让他去尝试获取锁,以此不断的往下延续

    参考:http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer

  • 相关阅读:
    Element-ui 复选框使用问题
    脚手架 oss 直传
    门外汉 avue使用
    cookie关闭浏览器清除
    关于(Incorrect string value: 'xF0x9Fx8ExBE' for column 'xxx' at row)报错
    扫码登录
    微信支付
    vue 腾讯地图 javaScriptAPI GL 多个markers自适应 (3)
    vue props 一次性传多个值
    vuex 的简单使用
  • 原文地址:https://www.cnblogs.com/xmzJava/p/8328127.html
Copyright © 2011-2022 走看看