美文网首页
CountDownLatch详解

CountDownLatch详解

作者: 95a6af369245 | 来源:发表于2019-01-21 17:26 被阅读126次

      功能描述

      一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

      常见用法

      多个人等一个信号后继续执行操作。例如5个运动员,等一个发令员的枪响。

      一个人等多个人的信号。旅游团等所有人签到完成才开始出发。

      我们最常见见到使用的地方是zk获取连接的时候

      final CountDownLatch countDownLatch=new CountDownLatch(1);

      ZooKeeper zooKeeper=new ZooKeeper(zk_url, time_out, new Watcher() {

      public void process(WatchedEvent watchedEvent) {

      Event.KeeperState state = watchedEvent.getState();

      Event.EventType type = watchedEvent.getType();

      if(Event.KeeperState.SyncConnected==state){

      if(Event.EventType.None==type){

      //调用此方法测计数减一

      countDownLatch.countDown();

      }

      }

      }

      });

      //阻碍当前线程进行,除非计数归零

      countDownLatch.await();

      这里了也可以看到很明确的使用方法,countDown直到0,await的线程才会继续执行。

      原理

      CountDownLatch内部使用了共享锁。如果这里还不知道共享和独占的区别,可以看前面的aqs速读。

      获取锁成功的方法很简单

      protected int tryAcquireShared(int acquires) {

      return (getState() == 0) ? 1 : -1;

      }

      共享锁有个约定,返回有三种情况。

      0为获取锁且没有其他资源

      正数 获取锁并且还有其他资源

      负数 获取锁资源失败

      共享锁在tryAcquireShared返回大于0的值的时候,会唤醒其他停顿状态加锁线程。由于没有对state的增加操作,所以当state变成0的时候,所有尝试加锁的线程都会被唤醒。

      protected boolean tryReleaseShared(int releases) {

      // Decrement count; signal when transition to zero

      for (;;) {

      int c = getState();

      if (c == 0)

      return false;

      int nextc = c-1;

      if (compareAndSetState(c, nextc))

      return nextc == 0;

      }

      }

      释放锁的操作,就是把state的值减一,当只有state变成0的时候,才返回true,tryReleaseShared返回true的时候会触发唤醒其他加锁线程的操作。

      通过上面的过程,我们可以看到CountDownLatch中的共享锁的加锁和释放锁的过程,下面看看是如何和CountDownLatch结合的。

      public CountDownLatch(int count) {

      if (count 0) throw new IllegalArgumentException(count 0);

      this.sync = new Sync(count);

      }

      CountDownLatch的构造里会初始化共享锁,并且设置state的值。

      public void countDown() {

      sync.releaseShared(1);

      }

      countDown是释放锁,最终会调用到tryReleaseShared。

      public void await() throws InterruptedException {

      sync.acquireSharedInterruptibly(1);

      }

      await是加锁,最终会调用到 tryAcquireShared。

      CountDownLatch就是一个不断释放锁的过程。

      常见问题

      CountDownLatch是不能够重用的

      根据上面的解析,大家也发现,共享锁加锁的操作并不会增加state的值。CountDownLatch中state一旦变成0就没有提供其他方式增长回去了。

      所以CountDownLatch是一次性消耗品,用完就得换新的。这样设计也是比较正常的,对状态的要求比较严格,例如都开车了你再告诉司机,这次加了5个人,车都开了一半了,不会考虑再回去。随意修改约束值会带来很多逻辑上的问题。

      CountDownLatch无限等待

      countDown没有被调用,那么await就会一直等下去。countDown常见没有被调用的情况:异常中断,线程池拒绝策略。

      可以使用

      public boolean await(long timeout, TimeUnit unit)

      根据业务情况,增加一个最大等待时间。使用这种方式,需要对失败的各种情况作出业务上的对应处理,否则就出现各种数据不正确的问题。也可以对countDown的线程做好异常处理,最好使用另外一个线程池来处理这些线程。这种情况就需要对业务不能停顿时间特别长,导致线程池的资源被耗光的情况做处理。如果是想通过new Thread避免,就需要考虑线程突然暴涨的问题。

    相关文章

      网友评论

          本文标题:CountDownLatch详解

          本文链接:https://www.haomeiwen.com/subject/ozthjqtx.html