CountDownLatch:Java中的同步工具

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: CountDownLatch:Java中的同步工具

多线程编程中,有时需要等待一个或多个线程完成它们的任务,然后再继续执行下一步操作。这种场景下,我们可以使用CountDownLatch来实现等待-通知机制。

理解CountDownLatch

CountDownLatch是Java中的一个同步工具,它允许一个或多个线程等待其他线程完成它们的操作后再继续执行。CountDownLatch包含一个计数器,该计数器初始化为一个正整数N。当一个线程完成一个操作时,计数器的值会减1。当计数器的值变为0时,所有等待中的线程将被释放。


CountDownLatch通常用于实现等待-通知机制,其中一个或多个线程等待其他线程完成它们的操作,然后再继续执行。例如,在一个多线程程序中,主线程可以使用CountDownLatch来等待所有工作线程完成它们的任务,然后再继续执行下一步操作。


使用CountDownLatch

下面是一个简单的示例,演示如何使用CountDownLatch:

import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        Thread t1 = new Thread(() -> {
            System.out.println("Thread 1 is running");
            latch.countDown();
        });
        Thread t2 = new Thread(() -> {
            System.out.println("Thread 2 is running");
            latch.countDown();
        });
        Thread t3 = new Thread(() -> {
            System.out.println("Thread 3 is running");
            latch.countDown();
        });
        t1.start();
        t2.start();
        t3.start();
        latch.await();
        System.out.println("All threads have completed their tasks");
    }
}

在这个例子中,我们创建了一个CountDownLatch对象,计数器的初始值为3。然后,我们创建了三个线程t1、t2和t3,它们各自完成自己的任务,并调用了CountDownLatch的countDown()方法来减少计数器的值。最后,我们调用CountDownLatch的await()方法来等待所有线程完成它们的任务。当计数器的值变为0时,await()方法将返回,主线程将继续执行下一步操作。


实践中的CountDownLatch

最近需要删除公司的S3上的大量文件以及对应的MySQL中存储的索引。

由于要删除的量级比较大,且公司的S3没有开放批量删除的接口,因此一开始引入了多线程:

public void physicallyDelete(List<String> idList) {
  int pageId = 1;
  boolean isHasNextPage;
  do {
    PageHelper.startPage(pageId, Constants.DEFAULT_PAGE_SIZE);
    List<Info> infoList = infoDAO.getByIdList(idList);
    PageInfo<Info> page = new PageInfo<>(infoList);
    isHasNextPage = page.isHasNextPage();
    Stopwatch stopwatch = Stopwatch.createStarted();
    infoList.forEach(info ->
      threadPool.execute(() -> {
          deleteS3AndMySQL(info);
      }));
    LOGGER.info("s3和数据库删除成功, size:{}, cost:{}", infoList.size(),
      stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
  } while (isHasNextPage);
}
private void deleteS3AndMySQL(Info info) {
  String key = getKeyFromS3Url(info.getVideoUrl());
  try {
    Stopwatch stopwatch = Stopwatch.createStarted();
    S3Manager.deleteFile(bucketName, key);
    LOGGER.info("s3删除成功, bucket:{}, key:{}, cost:{}", bucketName, key,
      stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
  } catch (Exception e) {
    LOGGER.error("s3删除失败, bucket:{}, key:{}", bucketName, key, e);
    return;
  } 
    infoDAO.physicallyDeleteByIdList(Collections.singletonList(info.getId()));
}


但是发现了一个问题,从理论上来讲,删除一个S3上的文件,应该对应删除一条MySQL上的记录;但是在日志中发现,每删除一条MySQL上的记录,就多次重复触发了删除S3上的对应文件。


排查发现,由于在physicallyDelete方法中存在分页查询,有可能在deleteS3AndMySQL方法中已经删除了S3,但尚未删除MySQL中的记录时,已经进行了分页查询下一页,线程池中的其他线程又运行了deleteS3AndMySQL方法,导致重复调用了S3的删除接口。


为了解决这个问题,我们可以引入CountDownLatch,代码如下:

public void physicallyDelete(List<String> idList) {
    int pageId = 1;
    boolean isHasNextPage;
    do {
      PageHelper.startPage(pageId, Constants.DEFAULT_PAGE_SIZE);
      List<Info> infoList = infoDAO.getByIdList(idList);
      PageInfo<Info> page = new PageInfo<>(infoList);
      isHasNextPage = page.isHasNextPage();
    CountDownLatch latch = new CountDownLatch(infoList.size());
      Stopwatch stopwatch = Stopwatch.createStarted();
      infoList.forEach(info ->
          threadPool.execute(() -> {
              try {
          deleteS3AndMySQL(info);
        } catch (Exception e) {
          LOGGER.info("deleteS3AndMySQL error", e);
        } finally {
          latch.countDown();
        }
        }));
    LOGGER.info("s3和数据库删除成功, size:{}, cost:{}", infoList.size(), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
      try {
          latch.await();
      } catch (Exception e) {
          LOGGER.info("latch.await异常", e);
          Thread.currentThread().interrupt();
      }
    } while (isHasNextPage);
}

这样,由于CountDownLatch的存在,就会等到线程池中的线程将分页查出的全部数据处理完毕后,再去查出下一页数据进行处理,从而避免多次重复调用S3删除接口。

总结

CountDownLatch是Java中的一个同步工具,它允许一个或多个线程等待其他线程完成它们的操作后再继续执行。CountDownLatch通常用于实现等待-通知机制,其中一个或多个线程等待其他线程完成它们的操作,然后再继续执行。在多线程编程中,CountDownLatch是一种非常有用的工具,可以帮助我们实现复杂的同步逻辑。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
26 0
|
2天前
|
Java
在Java编程的广阔天地中,条件语句是控制程序流程、实现逻辑判断的重要工具。
在Java编程中,if-else与switch作为核心条件语句,各具特色。if-else以其高度灵活性,适用于复杂逻辑判断,支持多种条件组合;而switch在多分支选择上表现优异,尤其适合处理枚举类型或固定选项集,通过内部跳转表提高执行效率。两者各有千秋:if-else擅长复杂逻辑,switch则在多分支选择中更胜一筹。理解它们的特点并在合适场景下使用,能够编写出更高效、易读的Java代码。
8 1
|
11天前
|
Java 调度 开发者
Java并发编程:解锁多线程同步的奥秘
在Java的世界里,并发编程是提升应用性能的关键所在。本文将深入浅出地探讨Java中的并发工具和同步机制,带领读者从基础到进阶,逐步掌握多线程编程的核心技巧。通过实例演示,我们将一起探索如何在多线程环境下保持数据的一致性,以及如何有效利用线程池来管理资源。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你对Java并发编程有更深入的理解和应用。
|
14天前
|
并行计算 Java API
Java中的Lambda表达式:简化代码的现代工具
在Java 8中引入的Lambda表达式,为函数式编程范式铺平了道路,提供了一种更简洁、更灵活的编写匿名方法的方式。本文将深入探讨Lambda表达式如何优化代码结构,提高开发效率,并通过具体示例展示其在实际应用中的魔力。
27 3
|
22天前
|
安全 Java 程序员
Java 并发编程:解锁多线程同步的奥秘
【7月更文挑战第30天】在Java的世界里,并发编程是一块充满挑战的领域。它如同一位严苛的导师,要求我们深入理解其运作机制,才能驾驭多线程的力量。本文将带你探索Java并发编程的核心概念,包括线程同步与通信、锁机制、以及并发集合的使用。我们将通过实例代码,揭示如何在多线程环境中保持数据的一致性和完整性,确保你的应用程序既高效又稳定。准备好了吗?让我们一同踏上这段解锁Java并发之谜的旅程。
26 5
|
23天前
|
算法 数据可视化 Java
JAVA规则引擎工具有哪些?
本文对比分析了六种Java规则引擎:Drools、IBM ODM (JRules)、Easy Rules、JBPM、OpenL Tablets以及Apache Camel结合规则组件的应用。Drools是一款功能全面的业务规则管理系统,支持DRL文件定义规则、高效的规则匹配算法、复杂的规则流及决策表,并易于与Java应用集成。IBM ODM (原JRules)提供了强大的规则管理功能,包括Web界面和Eclipse插件定义管理规则、直观的决策表和决策树、REST和Java API集成选项及优化的性能。
|
24天前
|
安全 Java API
Java并发编程的艺术:解锁多线程同步与协作的秘密
【7月更文挑战第28天】在Java的世界中,并发编程如同一场精心编排的交响乐,每一个线程都是乐团中的乐手,而同步机制则是那指挥棒,确保旋律的和谐与统一。本文将深入探讨Java并发编程的核心概念,包括线程的创建、同步机制、以及线程间的通信方式,旨在帮助读者解锁Java多线程编程的秘密,提升程序的性能和响应性。
29 3
|
29天前
|
Java 关系型数据库 开发工具
Java开发者必备技能与工具:构建高效开发环境的指南
【7月更文挑战第23天】作为Java开发者,掌握核心技能、熟练使用开发工具与框架、不断提升自己的软技能是构建高效开发环境的关键。希望本文能够为广大Java开发者提供一些有价值的参考和启示,助力大家在Java开发的道路上越走越远。
|
9天前
|
监控 Java
JAVA性能优化- IntelliJ插件:java内存分析工具(JProfiler)
JAVA性能优化- IntelliJ插件:java内存分析工具(JProfiler)
20 0
|
1月前
|
Java 测试技术 开发者
Java面试题:解释CountDownLatch, CyclicBarrier和Semaphore在并发编程中的使用
Java面试题:解释CountDownLatch, CyclicBarrier和Semaphore在并发编程中的使用
44 11