Skip to content

并发编程之 CountDownLatch 发令枪

闭锁,CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

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

应用场景:实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。 例如,我们想测试一个单例类。如果我们创建一个初始计数为 1 的 CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次 countDown()方法就可以让所有的等待线程同时恢复执行。开始执行前等待 n 个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有 N 个外部系统已经启动和运行了,例如处理 excel 中多个表单。

CountDownLatch 是一个倒计时锁;某个线程 t 等待倒计时为 0 的时候才执行;所谓的倒计时其实就是一个 int 类型的变量,在初始化 CountDownLatch 的时候会给他一个初始值(程序员定的);在多线程工作的时候可以通过 countDown() 方法来对计数器 -1;当等于 0 的时候 t 则会解阻塞运行

基本语法

java
//初始化对象,给一个初始值
CountDownLatch latch = new CountDownLatch(3);

//x线程 调用await阻塞 等待计数器为0的时候才会解阻塞
latch.await();

//其他线程调用countDown();对计数器-1
latch.countDown();

代码示例一

java
package com.mengweijin.learning.basic.lock;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 模拟高并发,启动 1000 个线程,每个线程对数字加 1 操作,同时开始
 * @author mengweijin
 */
@Slf4j
public class CountDownLatchDemo1 {

    private static final AtomicInteger atomicInteger = new AtomicInteger();

    private static int times = 1000;

    @SneakyThrows
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(times);
        CountDownLatch main = new CountDownLatch(times);

        for (int i = 0; i < times; i++) {
            new Thread(() -> {
                try {
                    //一直阻塞当前线程,直到计时器的值为 0,保证同时并发
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 每个线程进行 +1 操作
                atomicInteger.incrementAndGet();

                main.countDown();
            }, "t" + i).start();

            countDownLatch.countDown();
        }

        // 主线程等待所以子线程执行完毕
        main.await();
        log.debug("" + atomicInteger.get());
    }
}

代码示例二

java
package com.mengweijin.learning.basic.lock;

import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 启动 3 个线程,每个线程达到 100% 时,继续执行主线程
 * @author mengweijin
 */
public class CountDownLatchDemo2 {

    @SneakyThrows
    public static void main(String[] args) {
        AtomicInteger integer = new AtomicInteger();
        ExecutorService executorService = Executors.newFixedThreadPool(3,
                runnable -> new Thread(runnable, "t" + integer.getAndIncrement())
        );

        CountDownLatch countDownLatch = new CountDownLatch(3);
        Random random = new Random();

        List<String> list = new ArrayList<>();
        list.add("");
        list.add("");
        list.add("");

        for (int i = 0; i < 3; i++) {
            int temp = i;
            executorService.submit(() -> {
                for (int j = 1; j <= 100; j++) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(random.nextInt(200));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // [t0(100%), t1(100%), t2(100%)]
                    String name = Thread.currentThread().getName() + "(" + j + "%)";
                    list.set(temp, name);
                    System.out.print("\r" + Arrays.toString(list.toArray()));
                }
                countDownLatch.countDown();
            });
        }

        // 等待 3 个线程准备完成,然后继续执行主线程
        countDownLatch.await();
        System.out.println("\n主线程继续运行");
        executorService.shutdown();
    }
}

CountDownLatch 和 join 区别

  1. join 是一定等待线程执行完成才解阻塞
  2. 当线程对象 Thread 对象不明确的时候不能用 join。例如当使用 ExecutorService 时,无法使用 Thread.join() 方法。