• JUC工具类_CyclicBarrier与CountDownLatch


    最近被问到CyclicBarrier和CountDownLatch相关的面试题,CountDownLatch平时工作中经常用到,但是CyclicBarrier没有用过,一时答不上来,因此简单总结记录一下

    1.什么是CyclicBarrier?

    1.1 概念

    CyclicBarrier(循环屏障)是Java中的一个同步辅助类,它允许一组线程相互等待,直到达到某个公共屏障点。它的工作方式是,在所有参与线程都到达屏障之前,它们会一直等待。一旦最后一个线程到达屏障,所有被屏障等待的线程将被释放,可以继续执行后续的任务。

    1.2 原理

    CyclicBarrier内部维护了一个计数器,用于追踪到达屏障点的线程数量。当线程调用了CyclicBarrier的await()方法时,它会被阻塞,直到达到指定的等待数量。当最后一个线程调用await()方法时,屏障将打开,所有被阻塞的线程都会被释放,并且屏障会被重置以便下一次使用。

    下面来看一下CyclicBarrier 主要属性

    // ReentrantLock 重入锁
    private final ReentrantLock lock = new ReentrantLock();
    // 条件锁,等达到一定数量了再唤醒
    private final Condition trip = lock.newCondition();
    // 需要等待的线程数量
    private final int parties;
    // 当唤醒的时候执行的命令
    private final Runnable barrierCommand;
    // 代
    private Generation generation = new Generation();
    // 当前这一代还需要等待的线程数
    private int count;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    CyclicBarrier 存在如下构造方法,parties表示需要等待的线程数
    在这里插入图片描述

    CyclicBarrier 中await()方法表示等待线程,await()方法的实现逻辑来自dowait()方法
    image-20231118010254634

    好比如CyclicBarrier内部维护了一个计数器

    image-20231118010352841

    1.3 使用案例

    CyclicBarrier可以用于解决需要多个线程协同工作的场景,例如将一个任务分成多个子任务并行执行,然后等待所有子任务完成后再继续执行后续操作。它也可以用于模拟多个线程相互等待的场景,直到所有线程都准备就绪,然后一起开始执行。

    /**
     * 

    Class: TaskParallelExecution

    *

    Description: 使用CyclicBarrier演示多任务同时执行

    * * @author zhouyi * @version 1.0 * @date 2023/11/18 */
    public class TaskParallelExecution { private static final int NUM_THREADS = 4; public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(NUM_THREADS, () -> { System.out.println("All tasks completed. Proceeding to next step."); }); for (int i = 0; i < NUM_THREADS; i++) { int threadNum = i; Thread t = new Thread(() -> { System.out.println("Task " + threadNum + " started."); // 模拟任务的执行 try { Thread.sleep(2000); // 假设任务需要2秒完成 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task " + threadNum + " completed."); try { barrier.await(); // 等待其他线程完成任务 } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("Task " + threadNum + " continues with the next step."); }); t.start(); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    image-20231118011338132

    在实际开发中,常见的还有如下几个业务场景,比如:

    • 数据处理并行化:在大规模数据处理中,可以将数据分成多个部分,每个部分由一个线程处理。使用CyclicBarrier,每个线程在处理完自己的数据后等待其他线程完成,然后进行下一步的数据合并或汇总操作。
    • 并行计算任务:在并行计算中,可以将一个大任务分解成多个子任务,并行处理。每个子任务处理一部分数据,然后等待其他子任务完成,最后合并结果。CyclicBarrier可用于同步各个子任务的执行。
    • 多线程任务协同:在某些场景下,多个线程需要协同工作,例如多个线程同时从不同的地方获取数据,然后合并处理这些数据。CyclicBarrier可用于等待所有线程都准备就绪后,再同时开始数据获取和处理的过程。

    这些是一些使用CyclicBarrier的实际生产中的常见案例。CyclicBarrier提供了一种方便的同步机制,使多个线程能够协同工作,等待彼此达到一个共同的屏障点,然后再继续执行后续操作。

    2.什么是CountDownLatch?

    2.1 概念

    CountDownLatch是Java中的一个同步辅助类,它允许一个或多个线程等待其他线程完成操作后再继续执行。它的工作方式与CyclicBarrier相反:它通过一个计数器来实现等待,计数器初始化为一个正整数,每个线程完成操作后会将计数器减1,直到计数器变为0,等待的线程才会被释放。

    2.2 原理

    CountDownLatch包含两个主要方法:

    • countDown():每当一个线程完成了所需的操作,调用该方法将计数器减1。
    • await():调用该方法的线程会阻塞,直到计数器变为0,即所有线程都完成了操作。

    CountDownLatch通常用于一个线程等待其他若干个线程完成某个操作后再继续执行。例如,主线程希望等待多个子线程完成初始化操作后再开始执行某个任务,可以使用CountDownLatch来实现。

    2.3 使用案例

    使用CountDownLatch实现倒计时功能,例如模拟三个线程,当三个线程都一起执行结束后再统一交给主线程处理。

    public class CountDownLatchExample {
        private static final int NUM_THREADS = 3;
    
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch latch = new CountDownLatch(NUM_THREADS);
    
            for (int i = 0; i < NUM_THREADS; i++) {
                Thread t = new Thread(() -> {
                    // 模拟每个线程的操作
                    System.out.println("Thread " + Thread.currentThread().getId() + " started.");
                    try {
                        Thread.sleep(2000); // 假设每个线程需要2秒完成操作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread " + Thread.currentThread().getId() + " completed.");
                    latch.countDown(); // 操作完成后将计数器减1
                });
                t.start();
            }
    
            System.out.println("Main thread is waiting for other threads to complete.");
            latch.await(); // 主线程等待计数器变为0
            System.out.println("All threads have completed. Proceeding with the main thread.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    以上案例执行结果如下

    image-20231118012905177

    除了以上案例外,实际开发中经常遇到类似的场景,例如采用多线程处理大数据量导入,我们可以通过多线程分批读取数据、校验、封装数据,当以上几个环节都成功后(CountDownLatch 减少为0时)就可以进行写库操作,可以认为没有数据缺失。

    3.总结

    CyclicBarrier和CountDownLatch是Java中的两个同步辅助类,它们有一些共同点,但也存在一些区别。

    共同点:

    1. 两者都是用于线程间的同步和协作,可以控制线程的执行顺序和并发性。
    2. 都使用一个计数器来控制等待和释放线程的操作。
    3. 都可以用于解决多线程之间的协同工作问题。

    区别:

    1. 计数器行为不同:
      • CyclicBarrier的计数器可以重用,当计数器减到0时,所有等待的线程会被释放,计数器会被重置为初始值,可以继续使用。
      • CountDownLatch的计数器只能减少一次,一旦减到0,所有等待的线程会被释放,计数器不能重置或再次使用。
    2. 等待操作方式不同:
      • CyclicBarrier使用await()方法进行等待,当线程调用await()时,它会阻塞直到所有线程都到达屏障点,然后一起继续执行。
      • CountDownLatch使用await()方法进行等待,当线程调用await()时,它会阻塞直到计数器变为0,然后线程被释放。
    3. 使用场景不同:
      • CyclicBarrier通常用于将一个大任务分解为多个子任务并行执行,然后等待所有子任务完成后再继续执行后续操作。它适用于多线程协同工作的场景。
      • CountDownLatch通常用于一个或多个线程等待其他线程完成操作后再继续执行。它适用于线程之间的等待和同步的场景。

    数器变为0,然后线程被释放。
    3. 使用场景不同:

    • CyclicBarrier通常用于将一个大任务分解为多个子任务并行执行,然后等待所有子任务完成后再继续执行后续操作。它适用于多线程协同工作的场景。
    • CountDownLatch通常用于一个或多个线程等待其他线程完成操作后再继续执行。它适用于线程之间的等待和同步的场景。

    CyclicBarrier和CountDownLatch都是用于线程间的同步和协作,但它们的计数器行为、等待操作方式和使用场景有所不同。CyclicBarrier适用于任务分解和并行执行的场景,而CountDownLatch适用于线程之间的等待和同步的场景。在选择使用时,需要根据具体需求来确定使用哪个类更合适。

  • 相关阅读:
    IPv6环境下的网络安全观察报告总结
    ElasticSearch
    常见8种数据结构
    服务器正文22:linux内核网络模块笔记:理解TCP连接建立过程、一条TCP连接多大内存、一台机器最多支持多少条TCP连接、网络优化建议(下)
    STM32快速入门(定时器之输入捕获)
    .NET 扩展官方 Logger 实现将日志保存到本地文件
    Tone Mapping Correction
    【数据库】拼接字段 使用别名
    【go从入门到精通】常用标准库的用法
    TCP/IP网络协议详解
  • 原文地址:https://blog.csdn.net/qq_37606901/article/details/134473346