2022年10月更文挑战6-多线程下事务回滚问题

135 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

多线程下事务回滚问题

前文

正常情况下事务直接采用@Transactional注解即可,而当采用多线程进行事务处理时,@Transactional无法回滚子线程下执行的sql语句。本文为一种多线程下事务的处理方式。文章内容主要是解决方案的分析及实现,难免内容存在一些不严谨之处。由于代码实现的篇幅比较长,因此省略了一些对逻辑整体没有影响但必须的代码,例如声明部分等等。

实现过程

/**
     * 多线程回滚
     * @return
     */
    public Object testMultiThread() {
        AtomicBoolean dealSuccess = new AtomicBoolean(true);
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        CountDownLatch rollbackCountDown = new CountDownLatch(1);
        for (int i = 0; i < threadNum; i++) {
            new Thread(new NewThread()).start();
        }
        try {
            countDownLatch.await();
            rollbackCountDown.countDown();
        } catch (InterruptedException e) {
           
        }
        return "success";
    }
class NewThread implements Runnable{
    // 省略变量声明及初始化

    @Override
    public void run() {
        this.logic();
    }

    void logic(){
        TransactionStatus status = null;
        try {
            DefaultTransactionDefinition definition  = new DefaultTransactionDefinition();
            definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            status = transactionManager.getTransaction(definition); // 获得事务状态
            //        业务代码,部分省略
            testTransactionalMapper.addData(testTransactionalPO);
            if(i == 3){
                throw new RestApiException(ExceptionInfoEnum.DATA_ACCESS_EXCEPTION);
            }
            cnt--;
            countDownLatch.countDown();
            rollbackCountDown.await();
            if(!this.dealSuccess.get()){
                transactionManager.rollback(status);
            }else{
                transactionManager.commit(status);
            }
        } catch (Exception e) {
//            需要进行回滚处理
            transactionManager.rollback(status);
            this.dealSuccess.set(false);
            countDownLatch.countDown();
        }
    }
}

主要的处理思想是采用countDownLatch关键字进行实现。首先在主线程中设置两个该关键字,其中一个与线程数保持一致,用于处理线程的同时完成。另一个关键字设置为1,用于展示是否已经出现异常需要回滚。各线程启动后,在主线程中采用countDownLatch.await进行阻塞,等待各线程的执行情况。各线程中首先进行业务逻辑的执行,执行完毕后执行使用countDownLatch.countDown,标志业务逻辑执行完毕,同时将标志异常等待的关键字rollbackCountDown.await进行阻塞,等待其他线程是否出现异常进行回滚。倘若各线程都正常执行,主线程会执行到countDownLatch.await后,此时主线程进行rollbackCountDown.countDown,释放各业务线程。倘若其中任意一个线程出现异常,则进入到catch部分的代码中。在该部分的代码中,需要首先实现countDownLatch.countDown,表示该线程执行完毕,同时应该将全局变量设置为出现异常,并将该线程代码进行回滚。当其他线程收到主线程继续执行的通知后,经过判断发现此时需要进行事务回滚,则直接回滚前面的业务代码。

如何进行事务的回滚

transactionManager.rollback(status);

在多线程中无法直接通过@Transactional进行事务的引入,需要手动进行事务的新建及提交回滚。如上面的代码所示,我们需要进行手动进行事务的回滚。

后记

  • 千古兴亡多少事?悠悠。不尽长江滚滚流。