CountDownLatch的简介

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、 ConcurrentHashMap和BlockingQueue,它们都存在于JUC (java.util.concurrent)包下

CountDownLatch原理

CountDownLatch是通过一个计数器来实现的,计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1(CountDownLatch.countDown()方法)。当计数器到达0时,表示所有的线程都已完成任务,,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。

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

应用场景

实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。

开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了,例如处理excel中多个表单。

img

注意:一个线程不一定只能做countDown一次,也可以countDown多次

CountDownLatch的示例

public class CountDownLatchTest {
    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));

    private static Random random = new Random();


    public static void execute(CountDownLatch countDownLatch) {
        //获取一个随机数
        long sleepTime = random.nextInt(10);
        long threadId = Thread.currentThread().getId();
        System.out.println("线程ID" + threadId + ",开始执行--countDown");

        try {
            //睡眠随机秒
            Thread.sleep(sleepTime * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //计数器减1
        countDownLatch.countDown();
        System.out.println("线程ID" + threadId + ",准备任务完成耗时:" + sleepTime + "当前时间" + System.currentTimeMillis());
        try {
            //线程等待其他任务完成后唤醒
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程ID" + threadId + ",开始执行任务,当前时间:" + System.currentTimeMillis());
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            threadPool.submit(() -> {
                execute(countDownLatch);
            });
        }
        //线程等待其他任务完成后唤醒
        countDownLatch.await();
        Thread.sleep(1000);
        threadPool.shutdown();
        System.out.println("全部任务执行完成");
    }
}

打印结果

线程ID13,开始执行–countDown
线程ID16,开始执行–countDown
线程ID15,开始执行–countDown
线程ID12,开始执行–countDown
线程ID14,开始执行–countDown
线程ID14,准备任务完成耗时:3当前时间1565159118048
线程ID16,准备任务完成耗时:4当前时间1565159119047
线程ID12,准备任务完成耗时:4当前时间1565159119048
线程ID15,准备任务完成耗时:6当前时间1565159121047
线程ID13,准备任务完成耗时:7当前时间1565159122048
线程ID13,开始执行任务,当前时间:1565159122048
线程ID14,开始执行任务,当前时间:1565159122048
线程ID12,开始执行任务,当前时间:1565159122048
线程ID16,开始执行任务,当前时间:1565159122048
线程ID15,开始执行任务,当前时间:1565159122049
全部任务执行完成

使用CountDownLatch压测

img

在实战项目中,我们除了使用 jemter 等工具进行压测外,还可以自己动手使用 CountDownLatch 类编写压测代码。可以说 jemter 的并发压测背后也是使用的 CountDownLatch。可见掌握 CountDownLatch 类的使用是有多么的重要。

CountDownLatch是Java多线程同步器的四大金刚之一,CountDownLatch能够使一个线程等待其他线程完成各自的工作后再执行。

private  void latchTest() throws InterruptedException {
      //压测线程数
      int testThreads = 300;
      final CountDownLatch start = new CountDownLatch(1);
      final CountDownLatch end = new CountDownLatch(testThreads);
      //创建线程池
      ExecutorService exce = Executors.newFixedThreadPool(testThreads);
      for (int i = 0; i < testThreads; i++) {
          exce.submit(() -> {
              try {
                  //启动后等待 唤醒
                  start.await();
                  //压测具体方法
                  testLoad();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } finally {
                  //结束CountDownLatch -1
                  end.countDown();
              }
          });

      }
      //连接池线程初始化完成 开始压测
      start.countDown();
      //压测完成后结束
      end.await();
      exce.shutdown();
  }

简简单单的几行代码就可以实现300的压测。