每日一博 - 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后,往下继续执行自己的业务。


相关文章
|
5月前
|
Java 网络虚拟化
从源码全面解析LinkedBlockingQueue的来龙去脉
从源码全面解析LinkedBlockingQueue的来龙去脉
|
10天前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
4月前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
|
5月前
|
存储 安全 Java
Java多线程基础-9:代码案例之阻塞队列(二)
Java多线程基础教程系列中,介绍了如何实现一个简单的阻塞队列(非泛型版本)。
41 0
|
5月前
|
消息中间件 存储 负载均衡
Java多线程基础-9:代码案例之阻塞队列(一)
阻塞队列是一种遵循先进先出原则的线程安全数据结构,它在队列满时会阻塞入队操作,队列空时会阻塞出队操作,常用于多线程间的协作,简化同步代码编写。Java中提供了`BlockingQueue`接口及其实现类,如`ArrayBlockingQueue`和`LinkedBlockingQueue`,用于实现生产者-消费者模型,以实现负载均衡和资源的有效利用,如削峰填谷,降低系统压力。
59 0
|
5月前
|
Java 编译器
从源码全面解析 ArrayBlockingQueue 的来龙去脉
从源码全面解析 ArrayBlockingQueue 的来龙去脉
|
数据采集 Java
Java线程池使用场景和方法分析
Java线程池使用场景和方法分析
83 0
每日一博 - Semaphore使用场景分析以及源码分析
每日一博 - Semaphore使用场景分析以及源码分析
68 0
|
Java
Java多线程优化之线程池的使用方法
Java多线程优化之线程池的使用方法
148 0
京东一面:说说 CompletableFuture 的实现原理和使用场景?我懵了。。(2)
京东一面:说说 CompletableFuture 的实现原理和使用场景?我懵了。。(2)
165 0