JUC与事务管理 之 AtomicBoolean 和 CountDownLatch 实现父子线程间事务的管理

本文详细介绍了如何使用Java并发工具类CountDownLatch和AtomicBoolean实现多线程间的通信与事务控制。在处理大量数据库写操作时,通过拆分任务并使用CompletableFuture异步执行,提高了处理速度。每个子线程执行完成后,计数器减1,并通过await()阻塞主线程,直至所有任务完成。当子线程异常时,利用AtomicBoolean标记并回滚事务。作者计划对现有代码进行二次封装,简化流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CountDownLatch 是java并发包下的一个计数器。通过每个线程执行完毕计数器减1和子线程共享的变量实现子线程间的通信控制。

  1. 单次请求时,含有过多的对数据库表的写操作与数据处理的逻辑,并且此处可以根据不同的表将这些操作拆分开来。为了更快的提高处理速度,此处想到了使用多线程去处理这些拆开的操作。
  2. 首先需要两个核心共享变量,其一是CountDownLatch,另一个则是AtomicBoolean 。这两个都是JUC包下的工具类,是线程安全的。
  3. CountDownLatch可以理解为一个共享的扣减的计数器,核心是JUC包下的AQS。初始化时需要指定计数器的值且不能小于等于0,当子线程执行完任务后调用countDownLatch.countDown() 方法则会将计数器的值扣去1,接着调用await() 方法将当前线程进行阻塞,直到计数器值为0且所有线程调用了await() 方法,将唤醒线程。
  4. 子线程执行的过程中出现异常,则将AtomicBoolean 的值置为false,当所有线程苏醒后会对这个共享变量的值进行判断,false则报错,通过注解@Transactional将子线程的事务进行回滚。子线程执行完毕后,父线程收到子线程的异常也进行回滚与异常处理,将整体的大事务拆成了父线程的事务与多个子线程的事务。
  5. 多线程的实现采用了 CompletableFuture 与 Spring的线程池。
  6. 父线程的逻辑代码:
@Transactional(rollbackFor = Exception.class)
    public Boolean saveNurseOrganizationStudio(NurseOrganizationStudioPcDetailVo pcDetailVo) {
        // ===义务逻辑代码省略===
        // 异步处理 子线程任务是否执行成功的标识
        AtomicBoolean successFlag = new AtomicBoolean(Boolean.TRUE);
        // 线程通信的计数器
        CountDownLatch countDownLatch = new CountDownLatch(2);
        // 工作室成员 保存处理逻辑
        CompletableFuture<Void> membersCompletableFuture = threadUtil.runAsync(() -> nurseOrganizationStudioMemberService.membersHandler(saveFlag, pcDetailVo, countDownLatch, successFlag));
        // 工作室服务 保存处理逻辑
        CompletableFuture<Void> servicesCompletableFuture = threadUtil.runAsync(() -> nurseOrganizationStudioServicesService.servicesHandler(saveFlag, pcDetailVo, countDownLatch, successFlag));
        try {
            CompletableFuture.allOf(membersCompletableFuture, servicesCompletableFuture).get();
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException(e.getMessage());
        }
        return Boolean.TRUE;
    }
  1. 对 CompletableFuture 的简单封装
// 线程池
	@Autowired
    private TaskExecutor taskExecutor;

    /**
     * 注入 Runnable
     * 获取基本的的 CompletableFuture
     */
    public CompletableFuture<Void> runAsync(Runnable runnable) {
        return CompletableFuture.runAsync(runnable, taskExecutor);
    }
  1. runnable代码,调用了childThreadTransactional(Runnable runnable, CountDownLatch countDownLatch, AtomicBoolean successFlag)方法
/**
    * 工作室成员 保存处理逻辑
    * @param countDownLatch 线程通信的计数器
    * @param successFlag    线程事务的成功与否标识
    **/
   @Transactional(rollbackFor = Exception.class)
   public void membersHandler(CountDownLatch countDownLatch, AtomicBoolean successFlag) {
       threadUtil.childThreadTransactional(() -> {
       // 业务代码
       }, countDownLatch, successFlag);
   }
  1. 抽出来的子线程的核心 通用代码。核心想法是将业务逻辑代码作为一个 runnable放在该方法里调用。
 /**
     * 多个子线程都有事务需要进行处理时使用,属性countDownLatch 与 属性successFlag需要在父线程创建,子线程调用的对象也需要加 事务注解通过抛异常让子线程进行回滚
     * 逻辑:
     * 1、执行代码块,异常则抛出并将事务状态改为false,此时调用方的 事务注解配的事务管理器将进行回滚
     * 2、代码块执行结束,将多线程的计数器计数 减1
     * 3、调用 countDownLatch.await,当前线程进入等待状态,当计数器为0时唤醒返回值为True,超时为False
     * 4、超时或者其他子线程异常 则抛出异常进行回滚
     *
     * @param runnable       要执行的代码块
     * @param countDownLatch 计数器
     * @param successFlag    子线程间共享的 任务异常与否的标识
     **/
    public void childThreadTransactional(Runnable runnable, CountDownLatch countDownLatch, AtomicBoolean successFlag) {
        try {
            runnable.run();
        } catch (Exception e) {
            successFlag.set(Boolean.FALSE);
            log.error(e.getMessage());
            commonUtil.throwBizException(e.getMessage());
        } finally {
            // 线程执行完毕计数器减1
            countDownLatch.countDown();
            try {
                // 阻塞线程,当所有任务执行完 countDownLatch.countDown() 代码后唤醒当前线程;超时返回false
                boolean await = countDownLatch.await(3L, TimeUnit.SECONDS);
                // 超时或者 其他子线程异常 将状态置为 false 也需要抛出异常
                Assert.isTrue(!await || !successFlag.get(), CommonEnum.ExceptionDesc.服务异常);
            } catch (InterruptedException e) {
                successFlag.set(Boolean.FALSE);
                log.error(e.getMessage());
                commonUtil.throwBizException(e.getMessage());
            }
        }
    }
  1. 代码已上线,亲测可用。
  2. 文末,该方法实现了但是过于繁琐,在后期我对其想进行二次封装,思路代码如下。大体思路是将 CountDownLatch 与 AtomicBoolean 的初始化抽出,将各个任务试做一个个 的Runnable 对象,并发的去执行。项目架构上 ThreadUtil是在base包中,没有 spring的依赖,因此无法对事务进行控制,该方案未亲测。原理同上,就是在 this.childThreadTransactional 方法调用时由该方法 加上事务注解 控制子线程的事务的回滚。
 /**
     * todo runnable.run() 是单独的事务,其他线程报错,通过异常控制正确的线程进行回滚但是事务已经执行完毕且提交了,不在事务管理器控制范围内,要使用此方法需要将该工具类提到外层在 childThreadTransactional() 方法上 加事务注解,由此处控制事务方可实现
     */
    /**
     * 多个需要事务管控的任务 异步处理 通过 CountDownLatch 保证异步事务的正确管理
     * 每个 Runnable 需要有独立的  @Transactional 注解,通过报错将单个任务回滚,通过 AtomicBoolean 的共享变量将异常告知其他线程 并报错进行回滚
     *
     * @param runnables
     * @return void
     * @Author chenkaimin
     * @date 10:43 2022/6/9
     **/
    public void multiTransactional(Runnable... runnables) {
        if (runnables.length == 0) {
            return;
        }
        CountDownLatch countDownLatch = new CountDownLatch(runnables.length);
        AtomicBoolean successFlag = new AtomicBoolean(Boolean.TRUE);
        List<CompletableFuture<Void>> completableFutures = Stream.of(runnables).map(runnable -> {
            // 异步
            CompletableFuture<Void> completableFuture = this.runAsync(() -> {
                // 执行需要 事务管理的 任务
                this.childThreadTransactional(() -> runnable.run(), countDownLatch, successFlag);
            });
            return completableFuture;
        }).collect(Collectors.toList());
        int size = completableFutures.size();
        try {
            // 执行
            CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[size])).get();
        } catch (Exception e) {
            log.error(e.getMessage());
            commonUtil.throwBizException(e.getMessage());
        }
    }

留言:老婆做了一个小红书账号,全是瓷砖方面的干货,大佬们如果有需求或者有兴趣可以移步了解一下,嘻嘻~

小红书地址,GO GO GO!!!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值