zoukankan      html  css  js  c++  java
  • 关于CountDownLatch

    一.CountDownLatch是什么

    CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。

    latch是门闩的意思,CountDown是倒数计数的意思。

    CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。

    当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

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

    二.CountDownLatch能做什么

    1、某一线程在开始运行前等待n个线程执行完毕。

    将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,

    在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

    2、实现多个线程开始执行任务的最大并行性。

    注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。

    做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

    3、死锁检测:

    一个非常方便的使用场景是你用N个线程去访问共享资源,在每个测试阶段线程数量不同,并尝试产生死锁。

    三.CountDownLatch原理

    CountDownLatch是通过一个计数器来实现的,计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1。

    当计数器到达0时,表示所有的线程都已完成任务,然后在闭锁上等待的线程就可以恢复执行任务。

    注:

    这是一个一次性操作 - 计数无法重置。 如果你需要一个重置的版本计数,考虑使用CyclicBarrier。

    CountDownLatch其底层主要是通过AQS的共享锁机制实现的,因此它的核心属性只有一个sync,它继承自AQS,

    同时覆写了tryAcquireSharedtryReleaseShared,以完成具体的实现共享锁的获取与释放的逻辑。

    CountDownLatch最核心的方法只有两个,一个是countDown方法,每调用一次,就会将当前的count减一,当count值为0时,就会唤醒所有等待中的线程;

    另一个是await方法,它有两种形式,一种是阻塞式,一种是带超时机制的形式,该方法用于将当前等待“门闩”开启的线程挂起,直到count值为0,

    这一点很类似于条件队列,相当于等待的条件就是count值为0,然而其底层的实现并不是用条件队列,而是共享锁。

    CountDownLatch是基于AbstractQueuedSynchronizer(AQS)实现的,其通过state作为计数器。

    构造CountDownLatch时初始化一个state,以后每调用countDown方法一次,state减1;当state=0时,唤醒在await上被挂起的线程。

    CountDownLatch的计数器state不能被重置,如果需要一种能重置count的版本,可以考虑使用CyclicBarrier。

     

    四.CountDownLatch使用

    CountDownLatch的一个非常典型的应用场景是:

    有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。

    假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,

    这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

    例子一:

    有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。

    记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用Java代码设计两个类,Worker代表工人,Boss代表老板,具体的代码实现如下:

    工人代码:

    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 + "正在干活!");  
        }  
          
    }  

    老板代码:

    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("工人活都干完了,老板开始检查了!");  
        }  
      
    }  

    测试代码:

    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();  
        }  
      
    }  

    运行结果:

    王二正在干活!  
    李四正在干活!  
    老板正在等所有的工人干完活......  
    张三正在干活!  
    张三活干完了!  
    王二活干完了!  
    李四活干完了!  
    工人活都干完了,老板开始检查了!  

    结果分析:发现是等所有的工人都干完了活,老板才来检查,可以肯定的每次运行的结果可能与上面不一样,但老板检查永远是在后面的。

    例子二:

    假如有这样一个需求,当需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。

    在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用join。代码如下:

    public class JoinCountDownLatchTest {
    public static void main(String[] args) throws InterruptedException { Thread parser1 = new Thread(new Runnable() { @Override public void run() { } }); Thread parser2 = new Thread(new Runnable() { @Override public void run() { System.out.println("parser2 finish"); } }); parser1.start(); parser2.start(); parser1.join(); parser2.join(); System.out.println("all parser finish"); } }

    join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait,代码片段如下,wait(0)表示永远等待下去。

    while (isAlive()) {
     wait(0);
    }

    直到join线程中止后,线程的this.notifyAll会被调用,调用notifyAll是在JVM里实现的,所以JDK里看不到。JDK不推荐在线程实例上使用wait,notify和notifyAll方法。

    而在JDK1.5之后的并发包中提供的CountDownLatch也可以实现join的这个功能,并且比join的功能更多。

    public class CountDownLatchTest {
            static CountDownLatch c = new CountDownLatch(2);
            public static void main(String[] args) throws InterruptedException {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(1);
                        c.countDown();
                        System.out.println(2);
                        c.countDown();
                    }
                }).start();
                c.await();
                System.out.println("3");
            }
    
        }

    CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。

    当调用一次CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await会阻塞当前线程,直到N变成零。

    由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。

    用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里。

  • 相关阅读:
    Go_海量用户即时通讯系统
    Golang redis学习指南
    Golang 操作_Redis
    十七、Redis
    十六、网络编程-tcp socket编程
    十五、反射
    十四、goroutine(协程)和channel(管道)
    Jmeter笔记(9)Jmeter 性能测试资源监控方法(本地与服务器)(转)
    Fiddler笔记(8)重装时清除已有证书及解决tunnel to 443问题
    Jmeter笔记(8)Jmeter与MySql连接
  • 原文地址:https://www.cnblogs.com/ZJOE80/p/12889321.html
Copyright © 2011-2022 走看看