CountDownLatch实战应用——实现异步多线程业务处理,异常情况回滚全部子线程

news2024/11/27 1:40:16
😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: CountDownLatch实战应用——实现异步多线程业务处理,异常情况回滚全部子线程
⏱️ @ 创作时间: 2023年12月1日7

在这里插入图片描述

目录

  • 1、概述
  • 2、实现
  • 3、方法说明:
  • 4、代码实例

1、概述

CountDownLatch是一个同步器工具类,用来协调多个线程之间的同步,能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行,不可重置使用。

2、实现

使用一个计数器进行实现,计数器初始值为线程的数量,当每一个线程完成自己任务后,计数器的值就会减一,当计数器的值为0时,在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

3、方法说明:

  • public void countDown():递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少.
  • public viod await() /boolean await(long timeout,TimeUnit unit) :使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回true值。当线程调用了CountDownLatch对象的该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:
    • 如果计数到达零,则该方法返回true值。
    • 如果当前线程,在进入此方法时已经设置了该线程的中断状态;或者在等待时被中断,则抛出InterruptedException,并且清除当前线程的已中断状态。
    • 如果超出了指定的等待时间,则返回值为false。如果该时间小于等于零,则该方法根本不会等待。参数:timeout-要等待的最长时间、unit-timeout 参数的时间单位

4、代码实例

Controller:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    @Resource
    private CountDownService countDownService;
 
     /**
     * CountDownLatch实现异步多线程不同业务处理,不同service,异常情况回滚全部子线程
     *
     * @return
     */   
    @GetMapping("/countDown/handleDataBack")
    public String countDownHandleDataBack() {
        countDownService.handleDataBack();
        return "success";
    }

Sevice:

@Service
@Slf4j
public class CountDownService {


    @Resource
    private TestMapper testMapper;


    @Resource
    private ApplicationContext applicationContext;
    
    /**
     * 主线程,同时调用多个个子线程进行业务处理,当其中一个子线程出现异常,则全部子线程进行回滚
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleDataBack() {
        AtomicBoolean errorTag = new AtomicBoolean(false);
        // 设置countDown大小,与异步执行的业务数量一样,比如2个
        CountDownLatch countDownLatch = new CountDownLatch(2);
        // 再创建一个CountDownLatch,大小固定为一,用于子线程相互等待,最后确定是否回滚
        CountDownLatch errorCountDown = new CountDownLatch(1);

        // 异步调用其他Service,执行业务处理
        CountDownService bean = applicationContext.getBean(CountDownService.class);
        bean.handleTestOne(countDownLatch, errorCountDown, errorTag);
        bean.handleTestTwo(countDownLatch, errorCountDown, errorTag);

        try {
            // 主线程阻塞
            countDownLatch.await();
            // 可以设置最大阻塞时间,防止线程一直挂起
            /*boolean await = countDownLatch.await(1, TimeUnit.SECONDS);
            if (!await) {
                // 超过时间子线程都还没有结束,直接都回滚
                errorTag.set(true);
            }*/
            log.info("继续执行主线程");

            // 继续执行后续的操作,比如insert、update等
            TestEntity entity = new TestEntity();
            entity.setId(new Random().nextInt(999999999));
            entity.setCount(1);
            entity.setCommodityCode("handleTestMain");
            entity.setMoney(new Random().nextInt(1000000));
            entity.setUserId("user-handleTestMain");
            testMapper.insert(entity);

        } catch (Exception e) {
            log.error("主线程业务执行异常");
            errorTag.set(true);
        } finally {
            // 主线程业务执行完成后,执行errorCountDown计时器减一,使得所有阻塞的子线程,继续执行进入到异常判断中
            errorCountDown.countDown();
        }

        // 如果出现异常
        if (errorTag.get()) {
            throw new RuntimeException("异步业务执行出现异常");
        }
        log.info("主线程执行完成");
    }

    /**
     * 子线程具体业务处理
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @Async
    public void handleTestOne(CountDownLatch countDownLatch, CountDownLatch errorCountDown, AtomicBoolean errorTag) {
        log.info("开始执行handleTestOne线程");
        // 模拟业务耗时
        ThreadUtil.sleep(2000);
        try {
            // 执行数据库操作
            TestEntity entity = new TestEntity();
            entity.setId(new Random().nextInt(999999999));
            entity.setCount(1);
            entity.setCommodityCode("handleTestOne");
            entity.setMoney(new Random().nextInt(1000000));
            entity.setUserId("user-handleTestOne");
            testMapper.insert(entity);

            // 模拟出现异常
            int a = 1 / 0;

        } catch (Exception e) {
            errorTag.set(true);
        }
        // 子线程中,业务处理完成后,利用countDown的特性,计数器减一操作
        countDownLatch.countDown();

        // 子阻塞,直到其他子线程完成操作
        try {
            errorCountDown.await();
        } catch (Exception e) {
            errorTag.set(true);
        }
        log.info("handleTestOne-子线程执行完成");
        if (errorTag.get()) {
            // 抛出异常,回滚数据
            throw new RuntimeException("handleTestOne-子线程业务执行异常");
        }
    }

    /**
     * 子线程具体业务处理
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @Async
    public void handleTestTwo(CountDownLatch countDownLatch, CountDownLatch errorCountDown, AtomicBoolean errorTag) {
        log.info("开始执行handleTestTwo线程");
        // 模拟业务耗时
        ThreadUtil.sleep(500);
        try {
            // 执行数据库操作
            TestEntity entity = new TestEntity();
            entity.setId(new Random().nextInt(999999999));
            entity.setCount(1);
            entity.setCommodityCode("handleTestTwo");
            entity.setMoney(new Random().nextInt(1000000));
            entity.setUserId("user-handleTestTwo");
            testMapper.insert(entity);

        } catch (Exception e) {
            errorTag.set(true);
        }
        // 子线程中,业务处理完成后,利用countDown的特性,计数器减一操作
        countDownLatch.countDown();

        // 子阻塞,直到其他子线程完成操作
        try {
            errorCountDown.await();
        } catch (Exception e) {
            errorTag.set(true);
        }
        log.info("handleTestTwo-子线程执行完成");
        if (errorTag.get()) {
            // 抛出异常,回滚数据
            throw new RuntimeException("handleTestTwo-子线程业务执行异常");
        }
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1319115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机期末复习

前言 发现很多人都写了单片机原理及接口技术课后习题的答案,但是也就只写了答案而已,可能是他们觉得太简单的缘故吧,我这里对此进行一下我近段时间复习的总结,本篇博客只展示选择题、填空题和判断题的答案,仅供参考&a…

MFC 程序执行流程

目录 MFC 程序启动 MFC 入口函数 程序执行流程总结 在Win32课程中WinMain由程序员自己实现,那么流程是程序员安排,但到了MFC中,由于MFC库实现WinMain,也就意味着MFC负责安排程序的流程。 MFC 程序启动 程序的启动,…

【JAVA】CyclicBarrier源码解析以及示例

文章目录 前言CyclicBarrier源码解析以及示例主要成员变量核心方法 应用场景任务分解与合并应用示例 并行计算应用示例 游戏开发应用示例输出结果 数据加载应用示例 并发工具的协同应用示例 CyclicBarrier和CountDownLatch的区别循环性:计数器的变化:用途…

神经网络可以计算任何函数的可视化证明

神经网络可以计算任何函数的可视化证明 对于神经网络,一个显著的事实就是它可以计算任何函数。 如下:不管该函数如何,总有神经网络能够对任何可能的输入x,输出值f(x) 即使函数有很多输入和输出&#xff0…

【每日一题】【12.17】746.使用最小花费爬楼梯

🔥博客主页: A_SHOWY🎥系列专栏:力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.题目链接 746. 使用最小花费爬楼梯https://leetcode.cn/problems/min-cost-climbing-stairs/ 2.题目详情 今天的每日一题又…

leetcode每日一题打卡

leetcode每日一题 746.使用最小花费爬楼梯162.寻找峰值 从2023年12月17日开始打卡~持续更新 746.使用最小花费爬楼梯 2023/12/17 代码 解法一 class Solution {public int minCostClimbingStairs(int[] cost) {int n cost.length;int[] dp new int[n1];dp[0] 0;dp[1] 0;…

RocketMQ系统性学习-SpringCloud Alibaba集成RocketMQ以及批量发送消息、消息过滤实战

文章目录 批量发送消息消息过滤 批量发送消息 批量发送消息可以减少网络的 IO 开销,让多个消息通过 1 次网络开销就可以发送,提升数据发送的吞吐量 虽然批量发送消息可以减少网络 IO 开销,但是一次也不能发送太多消息 批量消息直接将多个消…

结构型设计模式(二)装饰器模式 适配器模式

装饰器模式 Decorator 1、什么是装饰器模式 装饰器模式允许通过将对象放入特殊的包装对象中来为原始对象添加新的行为。这种模式是一种结构型模式,因为它通过改变结构来改变被装饰对象的行为。它涉及到一组装饰器类,这些类用来包装具体组件。 2、为什…

将mjpg格式数转化成opencv Mat格式

该博客可以解决如下两个问题: 1、将mjpg格式数据转化成opencv Mat格式 2、v4l2_buffer 格式获取的mjpg格式数据转换成Mat格式。 要将 MJPEG 格式的数据转换为 OpenCV 的 Mat 格式,您可以使用 imdecode 函数。imdecode 函数可以将图像数据解码为 Mat 对象…

【MySQL】数据库和表的操作

数据库和表的操作 一、数据库的操作1. 创建数据库2. 字符集和校验规则(1)查看系统默认字符集以及校验规则(2)查看数据库支持的字符集(3)查看数据库支持的字符集校验规则(4)校验规则对…

B01、JVM与Java体系结构-01

字节码与多语言混合编程 字节码概述: 我们平时说的java字节码,指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为:jvm字节码。不同的编译器,可以编译出相同的字节码文件&…

05 动态渲染数据

概述 实际上动态渲染数据&#xff0c;在《使用CDN开发Vue3项目》中就已经学习过了&#xff0c;核心代码如下&#xff1a; <div id"vue-app">{{text}}</div> <script src"https://cdn.staticfile.org/vue/3.0.5/vue.global.js"></sc…

jsp属性访问控制管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

背景&#xff1a;为了解决共享数据授权访问的管理问题&#xff0c;出现了早期的自主访问控制&#xff08;Discretionary Access Control&#xff0c;DAC&#xff09;和强制访问控制&#xff08;Mandatory Access Control&#xff0c;MAC&#xff09;&#xff0c;随着计算机和技…

STM32F103RCT6开发板M3单片机教程06--定时器中断

前言 除非特别说明&#xff0c;本章节描述的模块应用于整个STM32F103xx微控制器系列&#xff0c;因为我们使用是STM32F103RCT6开发板是mini最小系统板。本教程使用是&#xff08;光明谷SUN_STM32mini开发板&#xff09; STM32F10X定时器(Timer)基础 首先了解一下是STM32F10X…

Unity中URP下的菲涅尔效果实现(URP下的法线和视线向量怎么获取)

文章目录 前言一、实现思路二、实现原理我们可以由下图直观的感受到 N 与 L夹角越小&#xff0c;点积越接近&#xff08;白色&#xff09;1。越趋近90&#xff0c;点积越接近0&#xff08;黑色&#xff09; 三、实现URP下的菲涅尔效果1、我们新建一个Shader&#xff0c;修改为最…

机器学习笔记 - 时间序列分析基础概念解释

一、简述 时间序列分析是一种统计方法,可检查定期收集的数据点以揭示潜在的模式。该技术与各个行业高度相关,因为它可以根据历史数据做出决策和预测。通过了解过去并预测未来,时间序列分析在金融、医疗保健、能源、供应链管理、天气预报、营销等领域发挥着至关重要的作用。 …

关于mysql存储过程中N/A和null的使用注意事项

oracle和mysql的存储过程大同小异&#xff0c;但是一些细节还是需要留意的。最近发现mysql的N/A和null在存储过程中容易忽略的一点&#xff0c;这会导致我们的存储过程提前结束。今天突然想起来了就记录一下。   mysql的N/A和null区别网上也说得很详细了&#xff0c;我就不赘…

频谱论文:面向频谱地图构建的频谱态势生成技术研究

#频谱# [1]李竟铭.面向频谱地图构建的频谱态势生成技术研究.2019.南京航空航天大学,MA thesis.doi:10.27239/d.cnki.gnhhu.2019.000556. &#xff08;南京航空航天大学&#xff09; 频谱地图是对无线电环境的抽象表达&#xff0c;它可以直观、多维度地展现频谱态势信息&…

部署智能合约以及 javascript 调用合约函数(Web3项目二实战之三)

在上一篇 智能合约是Web3项目的核心要务(Web3项目二实战之二) ,我们已然为项目编写了智能合约,在攥写完智能合约后,该项目将完成了一大部分,剩下无非就是用户界面交互的内容。 然而,在码完了智能合约代码后,起着承前启后关键性的便是,前端界面与智能合约的交互。 智能…

scroll-behavior属性使用方法

定义和用法&#xff1a; scroll-behavior 属性规定当用户单击可滚动框中的链接时&#xff0c;是否平滑地&#xff08;具动画效果&#xff09;滚动位置&#xff0c;而不是直线跳转。 <style>element{/* 核心代码 */scroll-behavior: smooth;} </style> 属性值&am…