Java多线程下使用TransactionTemplate控制事务

简介

本文展示了在Java的多线程环境下使用Spring的TransactionTemplate控制事务的提交与回滚,当任何一个子线程出现异常时,所有子线程都将回滚

环境

JDK:1.8.0_211

SpringBoot:2.5.10

说明

本文通过同时写入用户(User)和用户详细信息(UserDetail)的Demo方式来展开介绍,所有的实体类和服务层就忽略不写了,只写一个控制器当中的实现,整体代码比较简洁明了

先注入需要的依赖

java 复制代码
    //用户普通信息
    @Autowired private IUserService userService;

    //用户详细信息    
    @Autowired private IUserDetailService userDetailService;
    
    //建立一个线程池
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    //注入手动事务控制模板    
    @Autowired private TransactionTemplate transactionTemplate;

一、成功

下面是整个手动控制事务的多线程方法,会写入成功

java 复制代码
    @RequestMapping("/")
    public String saveUser(){
        int len = 10;
        final CountDownLatch latch = new CountDownLatch(len);
        AtomicBoolean b = new AtomicBoolean(false);
        for (int i = 0; i < len; i++) {
            final int j = i;
            executor.submit(()->{
                transactionTemplate.execute(transactionStatus ->{
                    try {
                        User user = new User();
                        user.setUserName("A"+j);
                        user.setPhone("15851885878");
                        userService.save(user);
                        UserDetail userDetail = new UserDetail();
                        userDetail.setUserId(user.getId());
                        int sex = j % 2 == 0 ? 1 : 0;
                        userDetail.setSex(sex);
                        userDetail.setAddress("江苏省南京市江宁区秣陵街道");
                        userDetailService.save(userDetail);
                    } catch (Exception e) {
                        log.info("保存用户信息发送异常!");
                        b.set(Boolean.TRUE);
                    } finally {
                        latch.countDown();
                    }
                    try {
                        latch.await();
                        if(b.get()){
                            log.info("========开始回滚========");
                            transactionStatus.setRollbackOnly();
                            log.info("========回滚完成========");
                        }
                    } catch (InterruptedException e) {
                        log.info("线程中断异常!");
                    }
                    return true;
                });
            });
        }
        return "OK";
    }

**核心对象:**AtomicBoolean、ExecutorService、TransactionTemplate。transactionTemplate.execute()方法接受一个事务的状态对象(TransactionStatus),当发生异常时,执行setRollbackOnly()即可回滚

**代码逻辑:**每次循环一次,线程计数器-1( latch.countDown() ),但是await()方法会使当前线程进入阻塞状态直至线程计数器=0 时才被唤醒(这也是为什么 " latch.countDown() " 要放在finally中的原因,如果线程计数器没有顺利 -1,线程最终无法被唤醒程序将卡死)。这时中间某个子线程发生了异常,由于所有的线程都是处于阻塞状态并没有执行提交,因此当线程被唤醒时发现AtomicBoolean这个对象的引用等于" true "了,所以都开始执行回滚操作。

内部解析:AtomicBoolean这个对象是线程安全的,其内部定义了一个被 " volatile " 修饰的变量" value "。学过并发编程的大概都知道线程对共享变量的工作方式是优先取三级缓存中的工作变量复制一份到自己的工作内存中使用,当某个线程因业务需要或发生异常时对于自身工作内存中的共享变量的修改(就是本文中的 "b"变量 )并同步回主存时,对于其他线程是不可知的(可见性(Visibility)问题),而关键字 " volatile "会强制清空其他线程中该变量的值,这样每个线程就都会获取到被修改后的最新的值,所以每个线程被唤醒时都发现变量b=true了,因此都回滚了

(volatile可以保证:可见性,有序性(禁止重排序) )

写入成功的截图

二、失败

已知上图是写入成功了,下面看看失败的时候会不会回滚,在方法中加入以下代码

java 复制代码
if(j == 8){
    //执行第九次时异常
    int div = 10/0;
}

现在把表中的数据清空

sql 复制代码
truncate 数据表名

注意:我建表时使用了ID自增(auto_increment),使用 " truncate " 可以连同auto_increment的标记一起清空,让auto_increment可以从1开始

代码执行后报异常,并且回滚了

数据也没有新增进去

成功

相关推荐
李慕婉学姐13 分钟前
【开题答辩过程】以《智能小区物业管理系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·数据库·后端
m***066817 分钟前
Spring Framework 中文官方文档
java·后端·spring
黎雁·泠崖22 分钟前
【魔法森林冒险】13/14 支线任务 & 计分系统:丰富性与结局
java·开发语言
SuniaWang22 分钟前
Spring AI 2.x 全面指南:架构升级、工具调用、多模型生态与实战示例
java·人工智能·后端·学习·spring·框架
闻哥23 分钟前
Elasticsearch查询优化实战:从原理到落地的全方位调优指南
java·大数据·elasticsearch·搜索引擎·面试·全文检索·springboot
sheji341626 分钟前
【开题答辩全过程】以 基于Java的甜品蛋糕网上商城的设计与实现为例,包含答辩的问题和答案
java·开发语言
智能零售小白白29 分钟前
零售多门店库存调拨优化:需求预测与路径规划的技术实现
java·开发语言·零售
前路不黑暗@31 分钟前
Java项目:Java脚手架项目的意义和环境搭建(一)
java·开发语言·spring boot·学习·spring cloud·maven·idea
Seven9741 分钟前
LockSupport深度解析:线程阻塞与唤醒的底层实现原理
java
组合缺一42 分钟前
OpenSolon v3.9.3, v3.8.5, v3.7.5, v3.6.8 年货版发布
java·人工智能·分布式·ai·llm·solon·mcp