zoukankan      html  css  js  c++  java
  • 多线程之CountDownLatch的用法及原理笔记

    前言-CountDownLatch是什么?

    CountDownLatch是具有synchronized机制的一个工具,目的是让一个或者多个线程等待,直到其他线程的一系列操作完成。

    CountDownLatch初始化的时候,需要提供一个整形数字,数字代表着线程需要调用countDown()方法的次数,当计数为0时,线程才会继续执行await()方法后的其他内容。
    CountDownLatch(int count);

    对象中的方法

    getCount:
    返回当前的计数count值,
    
    public void countDown()
    调用此方法后,会减少计数count的值。
    递减后如果为0,则会释放所有等待的线程
    
    public void await()
               throws InterruptedException
    调用CountDownLatch对象的await方法后。
    会让当前线程阻塞,直到计数count递减至0。
    

    如果当前线程数大于0,则当前线程在线程调度中将变得不可用,并处于休眠状态,直到发生以下两种情况之一:

    1、调用countDown()方法,将计数count递减至0。

    2、当前线程被其他线程打断。

    public boolean await(long timeout,
                TimeUnit unit)
                  throws InterruptedException
    

    同时await还提供一个带参数和返回值的方法。

    如果计数count正常递减,返回0后,await方法会返回true并继续执行后续逻辑。

    或是,尚未递减到0,而到达了指定的时间间隔后,方法返回false。

    如果时间小于等于0,则此方法不执行等待。

    实际案例

    join阻塞等待线程完成

    首先建立3个线程。

    public class Worker1 implements Runnable {
        @Override
        public void run() {
            System.out.println("-线程1启动");
            try {
                Thread.sleep(13_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1完成--我休眠13秒
    ");
        }
    }
    
    public class Worker2 implements Runnable {
        @Override
        public void run() {
            System.out.println("-线程2启动");
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2完成--我休眠3秒
    ");
        }
    }
    
    public class Worker3 implements Runnable {
        @Override
        public void run() {
            System.out.println("-线程3启动");
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3完成--我休眠6秒
    ");
            System.out.println();
        }
    }
    
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            Worker1 worker1 = new Worker1();
            Worker2 worker2 = new Worker2();
            Worker3 worker3 = new Worker3();
    
            Thread thread1 = new Thread(worker1,"线程1");
            Thread thread2 = new Thread(worker2,"线程2");
            Thread thread3 = new Thread(worker3,"线程3");
    
            thread1.start();
            thread2.start();
            thread3.start();
    
            thread1.join();
            thread2.join();
            thread3.join();
            System.out.println("主线程结束....");
    
        }
    }
    

    打印结果如下:

    -线程3启动
    -线程2启动
    -线程1启动
    线程2完成--我休眠3秒
    线程3完成--我休眠6秒
    
    线程1完成--我休眠13秒
    
    主线程结束....
    Process finished with exit code 0
    

    可以看出三个线程是并行执行的。启动顺序,并不和执行完毕的顺序一致,但可以明确的是,主线程为一直阻塞,直到三个线程执行完毕。

    CountDownLatch用法

    阿里巴巴的数据库连接池Druid中也用了countDownLatch来保证初始化。
    file

    // 开启创建连接的线程,如果线程池createScheduler为null,
    //则开启单个创建连接的线程
    createAndStartCreatorThread();  
    
     // 开启销毁过期连接的线程
    createAndStartDestroyThread(); 
    

    自己编写一个例子:
    这里模拟一种情况:
    主线程 依赖 线程A初始化三个数据,才能继续加载后续逻辑。

    file

    public class CountDownArticle {
        /**
         * 模拟 主线程 依赖 线程A初始化一个数据,才能继续加载后续逻辑
         */
        public static void main(String[] args) throws InterruptedException {
            AtomicReference<String> key = new AtomicReference<>("");
            CountDownLatch countDownLatch = new CountDownLatch(3);
                Thread t = new Thread(() -> {
                try {
    
                    //休眠5秒,模拟数据的初始化
                    TimeUnit.SECONDS.sleep(5);
    
                    key.set("核心秘钥123456");
                    System.out.println("数据1初始化完毕");
    
                    //释放---此处可以在任何位置调用,很灵活
                    countDownLatch.countDown();
    
                    System.out.println("数据2初始化完毕");
                    countDownLatch.countDown();
    
                    System.out.println("数据3初始化完毕");
                    countDownLatch.countDown();
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            });
            t.start();
    
            //等待数据初始化,阻塞
            countDownLatch.await();
            System.out.println("key:" + key.get());
        }
    }
    

    打印内容如下:

    数据1初始化完毕
    数据2初始化完毕
    数据3初始化完毕
    key:核心秘钥123456
    

    CountDownLatch和Join用法的区别?

    在使用join()中,多个线程只有在执行完毕之后欧才能被解除阻塞,而在CountDownLatch中,线程可以在任何时候任何位置调用countdown方法减少计数,通过这种方式,我们可以更好地控制线程的解除阻塞,而不是仅仅依赖于连接线程的完成。

    join()方法的执行逻辑如下图所示:

    file

    原理

    从源码可以看出,CountDownLatch是依赖于AbstractQueuedSynchronizer来实现这一系列逻辑的。

    队列同步器AbstractQueuedSynchronizer
    是一个用来构建锁和同步器的框架,它在内部定义了一个被标识为volatile的名为state的变量,用来表示同步状态。

    多个线程之间可以通过AQS来独占式或共享式的抢占资源。

    并且它通过内置的FIFO队列来完成线程的排队工作。

    file

    CountDownLatch中的Sync会优先尝试修改state的值,来获取同步状态。例如,如果某个线程成功的将state的值从0修改为1,表示成功的获取了同步状态。 这个修改的过程是通过CAS完成的,所以可以保证线程安全。

    反之,如果修改state失败,则会将当前线程加入到AQS的队列中,并阻塞线程。

    总结

    CountDownLatch(int N) 中的计数器,可以让我们支持最多等待N个线程的操作完成,或是一个线程操作N次。

    如果仅仅只需要等待线程的执行完毕,那么join可能就能满足。但是如果需要灵活的控制线程,使用CountDownLatch。

    注意事项

    countDownLatch.countDown();

    这一句话尽量写在finally中,或是保证此行代码前的逻辑正常运行,因为在一些情况下,出现异常会导致无法减一,然后出现死锁。

    CountDownLatch 是一次性使用的,当计数值在构造函数中初始化后,就不能再对其设置任何值,当 CountDownLatch 使用完毕,也不能再次被使用。

    写在最后

    为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
    问题时,都可以在这个大家庭里寻求帮助。

    公众号回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
    如果你也经历过遇到项目难题,无从下手,
    他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
    同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。

    欢迎来公众号【侠梦的开发笔记】,回复干货,领取精选学习视频一份

  • 相关阅读:
    C#在window服务配置Log4Net.dll
    致于即将逝去的2108年,2019年您好
    关于:未能加载文件或程序集“ICSharpCode.SharpZipLib”或它的某一个依赖项异常的解决方案
    Vs 中关于项目中的某 NuGet 程序包还原失败:找不到“xxx”版本的程序包“xxx”
    Git分布式版本控制器常用命令和使用
    微信公众平台网页登录授权多次重定向跳转,导致code使用多次问题
    Visual Studio高效实用的扩展工具、插件
    关于微信企业付款到零钱X509Certificate2读取证书信息,发布到服务器访问不到的解决方案
    关于ASP.NET MVC 项目在本地vs运行响应时间过长无法访问时,解决方法!
    彻底关闭windows10自动更新解决方案
  • 原文地址:https://www.cnblogs.com/hyq0823/p/12271402.html
Copyright © 2011-2022 走看看