一文搞懂 CountDownLatch 用法和源码!(一)

简介: CountDownLatch 是多线程控制的一种工具,它被称为 门阀、 计数器或者 闭锁。这个工具经常用来用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。下面我们就来一起认识一下 CountDownLatch

CountDownLatch 是多线程控制的一种工具,它被称为 门阀计数器或者 闭锁。这个工具经常用来用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。下面我们就来一起认识一下 CountDownLatch

认识 CountDownLatch

CountDownLatch 能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经任务了,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。

CountDownLatch 的使用

CountDownLatch 提供了一个构造方法,你必须指定其初始值,还指定了 countDown 方法,这个方法的作用主要用来减小计数器的值,当计数器变为 0 时,在 CountDownLatch 上 await 的线程就会被唤醒,继续执行其他任务。当然也可以延迟唤醒,给 CountDownLatch 加一个延迟时间就可以实现。

微信图片_20220418191519.png

其主要方法如下

微信图片_20220418191522.png

CountDownLatch 主要有下面这几个应用场景

CountDownLatch 应用场景

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

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

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

CountDownLatch 用法

下面我们通过一个简单的计数器来演示一下 CountDownLatch 的用法

public class TCountDownLatch {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(5);
        Increment increment = new Increment(latch);
        Decrement decrement = new Decrement(latch);
        new Thread(increment).start();
        new Thread(decrement).start();
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Decrement implements Runnable {
    CountDownLatch countDownLatch;
    public Decrement(CountDownLatch countDownLatch){
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        try {
            for(long i = countDownLatch.getCount();i > 0;i--){
                Thread.sleep(1000);
                System.out.println("countdown");
                this.countDownLatch.countDown();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Increment implements Runnable {
    CountDownLatch countDownLatch;
    public Increment(CountDownLatch countDownLatch){
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        try {
            System.out.println("await");
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Waiter Released");
    }
}

在 main 方法中我们初始化了一个计数器为 5 的 CountDownLatch,在 Decrement 方法中我们使用 countDown 执行减一操作,然后睡眠一段时间,同时在 Increment 类中进行等待,直到 Decrement 中的线程完成计数减一的操作后,唤醒 Increment 类中的 run 方法,使其继续执行。

下面我们再来通过学生赛跑这个例子来演示一下 CountDownLatch 的具体用法

public class StudentRunRace {

CountDownLatch stopLatch = new CountDownLatch(1);
CountDownLatch runLatch = new CountDownLatch(10);
public void waitSignal() throws Exception{
System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令");
stopLatch.await();
System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
runLatch.countDown();
}
public void waitStop() throws Exception{
Thread.sleep((long) (Math.random() * 10000));
System.out.println("裁判"+Thread.currentThread().getName()+"即将发布口令");
stopLatch.countDown();
System.out.println("裁判"+Thread.currentThread().getName()+"已发送口令,正在等待所有选手到达终点");
runLatch.await();
System.out.println("所有选手都到达终点");
System.out.println("裁判"+Thread.currentThread().getName()+"汇总成绩排名");
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
StudentRunRace studentRunRace = new StudentRunRace();
for (int i = 0; i < 10; i++) {
Runnable runnable = () -> {
try {
studentRunRace.waitSignal();
} catch (Exception e) {
e.printStackTrace();
}
};
service.execute(runnable);
}
try {
studentRunRace.waitStop();
} catch (Exception e) {
e.printStackTrace();
}
service.shutdown();
}
}

下面我们就来一起分析一下 CountDownLatch 的源码

CountDownLatch 源码分析

CountDownLatch 使用起来比较简单,但是却非常有用,现在你可以在你的工具箱中加上 CountDownLatch 这个工具类了。下面我们就来深入认识一下 CountDownLatch。

CountDownLatch 的底层是由 AbstractQueuedSynchronizer 支持,而 AQS 的数据结构的核心就是两个队列,一个是 同步队列(sync queue),一个是条件队列(condition queue)

            </div>
目录
相关文章
CountDownLatch实现原理全面解析
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步(即:用于线程之间的通信而不是互斥)。它允许一个或多个线程进入等待状态,直到其他线程执行完毕后,这些等待的线程才继续执行。
|
5月前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
58 0
|
2月前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
7月前
|
Java 测试技术
Java多线程的一些基本例子
【5月更文挑战第17天】Java多线程允许并发执行任务。示例1展示创建并启动两个`MyThread`对象,各自独立打印&quot;Hello World&quot;。示例2的`CounterExample`中,两个线程(IncrementThread和DecrementThread)同步地增加和减少共享计数器,确保最终计数为零。这些例子展示了Java线程的基本用法,包括线程同步,还有如Executor框架和线程池等更复杂的用例。
61 0
|
6月前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
CyclicBarrier 和 CountDownLatch 的实现原理与代码演示
CyclicBarrier 和 CountDownLatch 的实现原理与代码演示
166 0
|
Java 开发者
JUC系列学习(三):ReentrantLock的使用、源码解析及与Synchronized的异同
`ReentrantLock`同`Synchronized`一样可以实现线程锁的功能,同样具有可重入性,除此之外还可以实现公平锁&非公平锁,其底层是基于`AQS`框架实现的。
|
Java API
CountDownLatch源码硬核解析
CountDownLatch源码硬核解析
150 0
CountDownLatch源码硬核解析
|
缓存 Java 编译器
JUC并发编程学习(十七) -5分钟搞懂volatile
JUC并发编程学习(十七) -5分钟搞懂volatile
JUC并发编程学习(十七) -5分钟搞懂volatile