zoukankan      html  css  js  c++  java
  • 一文搞懂 CountDownLatch 用法和源码!

    版权声明:本文为CSDN博主「Java后端何哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/CSDN2497242041/article/details/115220672

    前言:多个线程同时查询一张表,最后汇总查询结果返回,那么就存在一个问题,如何判断多个线程是否全部已经处理完成。CountDownLatch 能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经完成任务了,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。

    一、CountDownLatch简介
    1、CountDownLatch概念

    CountDownLatch 是多线程控制的一种工具,它被称为 门阀、 计数器或者 闭锁。这个工具经常用来用来协调多个线程之间的同步,使一个线程等待其他线程各自执行完毕后再执行。

    CountDownLatch是在Java1.5被引入,存在于java.util.cucurrent包下。跟它一起被引入的工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue。

    CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

    Java的concurrent包里面的CountDownLatch其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。你可以向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。

    2、CountDownLatch类源码

    countDownLatch类中只提供了一个构造器:

    //参数count为计数值
    public CountDownLatch(int count) { };
    类中有三个方法是最重要的:

    //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
    public void await() throws InterruptedException { };
    //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
    //将count值减1
    public void countDown() { };
    CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

    举个例子,有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用Java代码设计两个类,Worker代表工人,Boss代表老板,具体的代码实现如下:

    Java代码 1

    package org.zapldy.concurrent;

    import java.util.Random;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;

    public class Worker implements Runnable{

    private CountDownLatch downLatch;
    private String name;

    public Worker(CountDownLatch downLatch, String name){
    this.downLatch = downLatch;
    this.name = name;
    }

    public void run() {
    this.doWork();
    try{
    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
    }catch(InterruptedException ie){
    }
    System.out.println(this.name + "活干完了!");
    this.downLatch.countDown();

    }

    private void doWork(){
    System.out.println(this.name + "正在干活!");
    }

    }
    Java代码 2

    package org.zapldy.concurrent;

    import java.util.concurrent.CountDownLatch;

    public class Boss implements Runnable {

    private CountDownLatch downLatch;

    public Boss(CountDownLatch downLatch){
    this.downLatch = downLatch;
    }

    public void run() {
    System.out.println("老板正在等所有的工人干完活......");
    try {
    this.downLatch.await();
    } catch (InterruptedException e) {
    }
    System.out.println("工人活都干完了,老板开始检查了!");
    }

    }
    Java代码 3

    package org.zapldy.concurrent;

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    public class CountDownLatchDemo {

    public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();

    CountDownLatch latch = new CountDownLatch(3);

    Worker w1 = new Worker(latch,"张三");
    Worker w2 = new Worker(latch,"李四");
    Worker w3 = new Worker(latch,"王二");

    Boss boss = new Boss(latch);

    executor.execute(w3);
    executor.execute(w2);
    executor.execute(w1);
    executor.execute(boss);

    executor.shutdown();
    }

    }
    当你运行CountDownLatchDemo这个对象的时候,你会发现是等所有的工人都干完了活,老板才来检查,下面是我本地机器上运行的一次结果,可以肯定的每次运行的结果可能与下面不一样,但老板检查永远是在后面的。

    输出结果:

    王二正在干活!

    李四正在干活!

    老板正在等所有的工人干完活......

    张三正在干活!

    张三活干完了!

    王二活干完了!

    李四活干完了!

    工人活都干完了,老板开始检查了!

    二、CountDownLatch 应用场景
    典型的应用场景就是当一个服务启动时,同时会加载很多组件和服务,这时候主线程会等待组件和服务的加载。当所有的组件和服务都加载完毕后,主线程和其他线程在一起完成某个任务。

    CountDownLatch 还可以实现学生一起比赛跑步的程序,CountDownLatch 初始化为学生数量的线程,鸣枪后,每个学生就是一条线程,来完成各自的任务,当第一个学生跑完全程后,CountDownLatch 就会减一,直到所有的学生完成后,CountDownLatch 会变为 0 ,接下来再一起宣布跑步成绩。

    顺着这个场景,你自己就可以延伸、拓展出来很多其他任务场景。

    示例:

    public class CountDownLatchTest {

    public static void main(String[] args) {
    final CountDownLatch latch = new CountDownLatch(2);
    System.out.println("主线程开始执行…… ……");
    //第一个子线程执行
    ExecutorService es1 = Executors.newSingleThreadExecutor();
    es1.execute(new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(3000);
    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    latch.countDown();
    }
    });
    es1.shutdown();

    //第二个子线程执行
    ExecutorService es2 = Executors.newSingleThreadExecutor();
    es2.execute(new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(3000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
    latch.countDown();
    }
    });
    es2.shutdown();
    System.out.println("等待两个线程执行完毕…… ……");
    try {
    latch.await();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("两个子线程都执行完毕,继续执行主线程");
    }
    }
    结果集:

    主线程开始执行…… ……
    等待两个线程执行完毕…… ……
    子线程:pool-1-thread-1执行
    子线程:pool-2-thread-1执行
    两个子线程都执行完毕,继续执行主线程
    模拟并发示例:

    public class Parallellimit {
    public static void main(String[] args) {
    ExecutorService pool = Executors.newCachedThreadPool();
    CountDownLatch cdl = new CountDownLatch(100);
    for (int i = 0; i < 100; i++) {
    CountRunnable runnable = new CountRunnable(cdl);
    pool.execute(runnable);
    }
    }
    }

    class CountRunnable implements Runnable {
    private CountDownLatch countDownLatch;
    public CountRunnable(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
    try {
    synchronized (countDownLatch) {
    /*** 每次减少一个容量*/
    countDownLatch.countDown();
    System.out.println("thread counts = " + (countDownLatch.getCount()));
    }
    countDownLatch.await();
    System.out.println("concurrency counts = " + (100 - countDownLatch.getCount()));
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    CountDownLatch和CyclicBarrier区别:
    1.countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
    2.CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

    参考链接:

    一文搞懂 CountDownLatch 用法和源码!
    ————————————————

  • 相关阅读:
    MSN、易趣、大旗被挂马 用户浏览后感染机器狗病毒 狼人:
    世界最大漏洞数据库发布最新研究报告 狼人:
    五角大楼最昂贵武器发展项目遭到黑客攻击 狼人:
    RSA呼吁厂商“创造性协作” 共同反击网络威胁 狼人:
    RSA2009:云计算服务如何保证安全? 狼人:
    黑客工具可将恶意软件隐藏于.Net框架 狼人:
    RSA安全大会将亮相25款热门安全产品 狼人:
    目录访问共享C#怎么访问共享目录
    代码下行Jquery结合Ajax和Web服务使用三层架构实现无刷新分页
    输出次数HDU2192:MagicBuilding
  • 原文地址:https://www.cnblogs.com/yuluoxingkong/p/15386265.html
Copyright © 2011-2022 走看看