JUC工具类_CyclicBarrier与CountDownLatch

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

1.什么是CyclicBarrier?

1.1 概念

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

1.2 原理

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

下面来看一下CyclicBarrier 主要属性

java 复制代码
// 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;

CyclicBarrier 存在如下构造方法,parties表示需要等待的线程数

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

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

1.3 使用案例

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

java 复制代码
/**
 * <p>Class: TaskParallelExecution</p>
 * <p>Description: 使用CyclicBarrier演示多任务同时执行</p>
 *
 * @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();
        }
    }
}

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

  • 数据处理并行化:在大规模数据处理中,可以将数据分成多个部分,每个部分由一个线程处理。使用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实现倒计时功能,例如模拟三个线程,当三个线程都一起执行结束后再统一交给主线程处理。

java 复制代码
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.");
    }
}

以上案例执行结果如下

除了以上案例外,实际开发中经常遇到类似的场景,例如采用多线程处理大数据量导入,我们可以通过多线程分批读取数据、校验、封装数据,当以上几个环节都成功后(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,然后线程被释放。

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

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

相关推荐
Java中文社群3 分钟前
Dify实战案例:MySQL查询助手!嘎嘎好用
java·人工智能·后端
MYH5164 分钟前
拉力测试cuda pytorch 把 4070显卡拉满
人工智能·pytorch·python
程序猿阿伟6 分钟前
《深度探秘:Java构建Spark MLlib与TensorFlow Serving混合推理流水线》
java·spark-ml·tensorflow
某人辛木9 分钟前
基于tensorflow实现的猫狗识别
人工智能·python·tensorflow
大白爱琴11 分钟前
使用python进行图像处理—图像变换(6)
图像处理·人工智能·python
TDengine (老段)14 分钟前
TDengine 开发指南—— UDF函数
java·大数据·数据库·物联网·数据分析·tdengine·涛思数据
键盘林18 分钟前
分布式系统简述
java·开发语言
可儿·四系桜19 分钟前
如何在 Java 中优雅地使用 Redisson 实现分布式锁
java·开发语言·分布式
月忆36424 分钟前
等待组(waitgroup)
前端·爬虫·python
Stanford_110630 分钟前
关于大数据的基础知识(二)——国内大数据产业链分布结构
大数据·开发语言·物联网·微信小程序·微信公众平台·twitter·微信开放平台