每日一博 - CountDownLatch使用场景分析以及源码分析

简介: 每日一博 - CountDownLatch使用场景分析以及源码分析

d0fdb2e70e1847b2b9749789048967d3.png

并发编程常用的工具类简介


我们先看并发编程中提供的几个常用的工具类

  • CountDownLatch : CountDownLatch 用于阻塞当前 1 个或多个线程,其目的是让这些线程等待其它线程的执行完成。

可以简单将其理解为一个计数器,当初始化一个 count=n 的 CountDownLatch 对象之后,需要调用该对象的 CountDownLatch#countDown 方法来对计数器进行减值,直到计数器为 0 的时候,等待该计数器的线程才能继续执行。


但是需要注意的一点是,执行 CountDownLatch#countDown 方法的线程在执行完减值操作之后,并不会因此而阻塞。真正阻塞等待事件的是调用 CountDownLatch 对象 CountDownLatch#await 方法的线程,该线程一直会阻塞直到计数器计数变为 0 为止。

  • CyclicBarrier :CyclicBarrier 用于阻塞当前多个线程,其目的是让这些线程彼此之间相互等待,当这些线程均到达屏障后再一起往下执行
  • Semaphore:信号量,可以通过控制“许可证”的数量,来保证线程之间的配合
  • Phaser:和CyclicBarrier类似,但计数可变
  • Exchanger :两个线程交换对象
  • Condition : 可以控制线程的“等待”和“唤醒” , Object.wait()的升级版本


CountDownLatch 概述


CountDownLatch位于java.util.cucurrent包下,Java1.5被引入。同时被引入的还有其他几个工具类比如: CyclicBarrier、Semaphore、ConcurrenthashMap和BlockingQueue等。


CountDownLatch : A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.


CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

fe94231583a649ff8abc4c6a5c786dab.png


CountDownLatch 是一个同步计数器, 通常用于一个线程或者多个线程等待另外一组线程执行完成之前一直等待。


CountDownLatch在使用时,会初始化一个计数器,计数器的数量为线程的数量。 每一个线程执行完成以后,就会调用countDown()方法将计数器的个数-1 ,直到计数器的个为0 , 表示所有线程都执行完毕, 这个时候调用await()方法的等待线程就可以恢复继续工作了。


源码分析


19e9dae876f64b5e8d159781e9d72bfa.png


可以看出 CountDownLatch是基于Sync类实现的,而Sync继承AQS, 是AQS共享模式

6166ef19181c48b1803ef8cc86eb1dd4.png

使用场景


使用场景一: 模拟高并发并发执行(让多个线程等待)


举个例子,我们打算200个并发同时去做一笔业务,我们如果使用代码该如何实现呢?


那简单呀,老哥。

我打算去模拟200并发去做业务,那我们知道CountDownLatch 是 等countDown() 逐个减一以后,直到为0, 调用await()的线程才去工作。

那我就模拟让这200个线程调用await() ,然后等 count=0的时候 一起搞业务呗。


MMP,让我想了公司到饭点,大家一起冲刺去干饭的场景 , 这个CountDownLatch 就是那墙壁上的钟表啊 ,滴答滴答 ,就等11:45分…


我们每个人都做了准备动作(类似countDown())

时间一到(就像那await()),我们每个干饭人就是那业务线程, 那可口的大餐就是那业务

来吧,show code

package com.artisan.juc;
import java.util.concurrent.CountDownLatch;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/5 1:42
 * @mark: show me the code , change the world
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        int cnt = 10;
        CountDownLatch countDownLatch = new CountDownLatch(cnt);
        // 模拟10个并发干饭
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    // 干饭人准备完毕……干饭人都阻塞在这,等待号令(cnt=0)
                    countDownLatch.await();
                    System.out.println("编号:" + Thread.currentThread().getName() + " 开始干饭...." + System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            // 模拟一部分业务耗时
            Thread.sleep(10);
            // 每个线程调用一次,cnt每次减一,直到为0,就是干饭信号
            countDownLatch.countDown();
            System.out.println("countDown执行一次,个数:" + (--cnt));
        }
    }
}

6c87a836ba7f4a1fb7abb969986bca04.png


可以看到,我们通过CountDownLatch#await()方法,让多个业务线程启动后阻塞等待, 等待主线程调用CountDownLatch#count() 方法将计数器减为0以后,让所有的业务线程一起并发运行,这样就实现了我们多个线程并发的目的。


049ced37ca5947df976056093baf6a8e.gif



使用场景二: 模拟异步执行后回到主线程的业务(让一个线程等待)

举个例子,我们有一些依赖关系的业务

String  str = method1();  // 耗时1S 
String  str2 = method2();// 耗时2S 
method3(str, str2); 


这mmp ,数据有依赖呀, 当然了,你可以用CompletableFuture优化,我们这里不讨论它嘛



3f621745e27344cd9c03d5e80c75a168.png


我们看看CountDownLacth该怎么玩?

分析一下这个业务场景, 让method 1 和 method 2 异步执行,然后执行完以后再回到主线程,取到返回结果汇总呗

我们简化下,不获取返回结果

package com.artisan.juc;
import java.util.concurrent.*;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/5 1:42
 * @mark: show me the code , change the world
 */
public class CountDownLatchTest2 {
    public static void main(String[] args) throws InterruptedException {
        // 2 为线程个数
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " --执行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + "**执行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        countDownLatch.await();
        System.out.println("主线业务继续执行");
    }
}

f3022a6d7559457b853c2023d4071d0a.png


我们可以看到: 在每个线程 完成的最后一行加上CountDownLatch#countDown(),让计数器-1;当所有线程完成-1,主线程之前阻塞在这里 countDownLatch.await(); ,直到计数器减到0后,往下继续执行自己的业务。


相关文章
|
2月前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
6月前
|
缓存 算法 安全
Java并发编程学习8-同步工具类
【4月更文挑战第2天】本篇介绍一下Java平台类库下的常用的同步工具类(闭锁、Future、信号量和栅栏)
47 2
Java并发编程学习8-同步工具类
|
6月前
|
算法 Go 调度
goroutine源码分析,直击并发底层实现
goroutine源码分析,直击并发底层实现
63 0
|
数据采集 Java
Java线程池使用场景和方法分析
Java线程池使用场景和方法分析
86 0
每日一博 - Semaphore使用场景分析以及源码分析
每日一博 - Semaphore使用场景分析以及源码分析
71 0
|
安全 Java
68. 对并发熟悉吗?说说Synchronized及实现原理
68. 对并发熟悉吗?说说Synchronized及实现原理
93 0
68. 对并发熟悉吗?说说Synchronized及实现原理
|
Java API Spring
java并发原理实战(4) -- 线程的创建方式
java并发原理实战(4) -- 线程的创建方式
122 0
java并发原理实战(4) -- 线程的创建方式
CountDownLatch原理简介和使用过程(1)
CountDownLatch原理简介和使用过程(1)
176 0
CountDownLatch原理简介和使用过程(1)
|
缓存
CountDownLatch原理简介和使用过程(2)
CountDownLatch原理简介和使用过程(2)
110 0
CountDownLatch原理简介和使用过程(2)
CountDownLatch原理简介和使用过程(3)
CountDownLatch原理简介和使用过程(3)
111 0
CountDownLatch原理简介和使用过程(3)