首页 > 其他分享 > CountDownLatch的使用

CountDownLatch的使用

时间:2023-01-16 09:23:31浏览次数:37  
标签:CountDownLatch 运动员 线程 接力 使用 就位 起点

一、介绍

CountDownLatch是一个计数的闭锁,作用与CyclicBarrier有点儿相似。

在 API中是这样描述的:
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

 • CountDownLatch :一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
 • CyclicBarrier :多个线程互相等待,直到到达同一个同步点,再继续一起执行。

对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后就可以恢复等待的线程继续执行了。

二、实现分析

CountDownLatch结构如下

通过上面的结构图我们可以看到, CountDownLatch内部依赖Sync实现,而Sync继承AQS。

CountDownLatch仅提供了一个构造方法:
CountDownLatch(int count) : 构造一个用给定计数初始化的 CountDownLatch

public CountDownLatch(int count) {
  if (count < 0) throw new IllegalArgumentException("count < 0");
  this.sync = new Sync(count);
}

sync 为CountDownLatch的一个内部类,通过这个内部类Sync可以知道CountDownLatch是采用共享锁来实现的。最长用的两个方法是await()和countDown():

 • CountDownLatch 提供await()方法来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。内部使用AQS的getState方法获取计数器,如果计数器值不等于0,则会以自旋方式会尝试一直去获取同步状态。
 • CountDownLatch 提供countDown() 方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态。

三、案例

在CyclicBarrier应用场景之上进行修改,添加接力运动员。

起点运动员应该等其他起点运动员准备好才可以起跑(CyclicBarrier)。

接力运动员不需要关心其他人,只需和自己有关的起点运动员到接力点即可开跑(CountDownLatch)。

public class Demo2CountDownLatch {
    public static void main(String[] args) {
        // 拦截线程的数量为5,即5个起点运动员
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            CountDownLatch countDownLatch = new CountDownLatch(1);
            // 5个起点运动员
            Thread t1 = new Thread(new Athlete(cyclicBarrier, countDownLatch,"起点运动员" + i));
            // 5个接力运动员
            Thread t2 = new Thread(new Athlete(countDownLatch, "接力运动员" + i));
            threadList.add(t1);
            threadList.add(t2);
        }
        for (Thread t : threadList) {
            t.start();
        }
    }
    static class Athlete implements Runnable {
        private CyclicBarrier cyclicBarrier;
        private String name;
        CountDownLatch countDownLatch;
        // 起点运动员,需要CyclicBarrier来等待所有运动员就位
        public Athlete(CyclicBarrier cyclicBarrier, CountDownLatch countDownLatch, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.countDownLatch = countDownLatch;
            this.name = name;
        }
        // 接力运动员
        public Athlete(CountDownLatch countDownLatch, String name) {
            this.countDownLatch = countDownLatch;
            this.name = name;
        }
        @Override
        public void run() {
            // 判断是否是起点运动员,不为Null说明是起点运动员
            if (cyclicBarrier != null) {
                System.out.println(name + "就位"); // 一开始所有5个起点运动员各就各位
                try {
                    cyclicBarrier.await(); // 当最后一个起点运动员就位后就开始起跑
                    System.out.println(name + "到达交接点。");
                    // 已经到达交接点,到达交接点后,计数到达0
                    countDownLatch.countDown();
                } catch (Exception e) {
                }
            }
            //判断是否是接力运动员
            if (cyclicBarrier == null) { // 如果为空说明是接力运动员
                System.out.println(name + "就位");
                try {
                    // 使当前线程在锁存器倒计数至零之前一直等待,到达0之后则释放所有等待的线程
                    countDownLatch.await();
                    System.out.println(name + "到达终点。");
              } catch (Exception e) {
              }
          }
      }
  }
}

结果如下:

起点运动员1就位
接力运动员1就位
起点运动员3就位
接力运动员3就位
起点运动员0就位
接力运动员0就位
起点运动员2就位
接力运动员2就位
起点运动员4就位
起点运动员4到达交接点。
接力运动员4就位
接力运动员4到达终点。
起点运动员3到达交接点。
起点运动员1到达交接点。
接力运动员3到达终点。
起点运动员2到达交接点。
起点运动员0到达交接点。
接力运动员2到达终点。
接力运动员1到达终点。
接力运动员0到达终点。

一开始,5个起点运动员就位。当最后一个起点运动员就位后,就开始起跑。当起点运动员到达接力点时,计数减1为0。由于接力运动员在锁存器倒计数至零之前一直等待,而计数到达0之后则接力运动员开始起跑,直至到达终点。

 

标签:CountDownLatch,运动员,线程,接力,使用,就位,起点
From: https://www.cnblogs.com/zwh0910/p/17014259.html

相关文章

  • Express中使用Session认证
             ......
  • DataGrip使用配置和技巧
    DataGrip使用配置和技巧 注:本文转自:https://www.cnblogs.com/zuge/p/7397255.html最近看到一款数据库客户端工具,DataGrip,是大名鼎鼎的JetBrains公司出品的,就是那个出......
  • git使用问题记录
    hint:Updateswererejectedbecausetheremotecontainsworkthatyoudo问题原因:远程仓库中含有本地仓库没有的文件直接拉取会拒绝解决方法:gitpulloriginm......
  • 8.使用注解开发
    在spring4之后,要使用注解开发,必须要保证aop的包导入了<dependencies><!--https://mvnrepository.com/artifact/org.springframework/spring-webmvc--><depend......
  • Windows安装并使用Make
    一、安装并配置MinGW:1.安装MinGW:https://sourceforge.net/projects/mingw/files/2.把MinGW安装目录下的bin文件夹(D:\Application\MinGW\bin)添加到环境变量/系统......
  • 使用Express写接口
                                  ......
  • 在使用KNN_cuda库中出现的问题小结
    什么是KNN_cuda​KNN_CUDA是一个用CUDA实现的计算k近邻搜索的项目。​KNN搜索是指通过计算query的向量和reference集合内的向量的相似度,并找出最相似的k个。......
  • AJAX使用记录
    目录什么是AJAXAJAX的工作流程省市二级联动案例AJAX的使用总结什么是AJAXAJAX=AsynchronousJavaScriptAndXML.我感觉AJAX是一个有点误导性的名称。让人觉得AJA......
  • simulink使用AWGN报错:When the 'Mode' parameter is set to 'Signal to noise ratio',
    原因:当“模式”参数设置为“信噪比”时,输入和输出必须有离散的采样时间。解决:输入端的信号设置sample time,即采样率;输出端增加0阶保持器,不然matlab无法计算 ......
  • 使用动态输出打印内核的DEBUG信息
    简介printk()是很多嵌入式开发者喜欢用的调试手段之一,但是,使用printk()每次都要重新编译内核,很不方便。使用动态输出在不需要重新编译内核的情况下,方便的打印出内核的debu......