当前位置: 首页 > news >正文

【多线程场景下事务失效问题如何处理?】

文章目录

    • 一.什么是多线程事务
    • 二.业务背景
    • 三.多线程事务解决方案
      • 1. 子线程新开一个事务
      • 2. 使用编程式事务
    • 四.小结

一.什么是多线程事务

多线程事务是指在多线程环境中处理事务的场景,其中多个线程可能同时执行数据库操作,并可能在同一事务中涉及多个资源或操作。这种情况下,需要确保事务的一致性、隔离性和完整性,以防止数据不一致或冲突。
关键点:

  • 并发性:多个线程可能同时访问和修改数据库,导致数据竞争。
  • 事务一致性:确保所有线程的操作要么全部成功(提交),要么在出现错误时全部失败(回滚)。
  • 隔离性:不同线程之间的事务应相互独立,避免相互干扰。

性能考虑:在多线程环境中,管理事务可能会影响性能,因此需要平衡一致性和系统效率。

注意:这里的多线程操作的是同一个数据源,非多个数据源,暂时不讨论分布式事务。

二.业务背景

多线程业务场景:我这里这是举例哈…假如我们新增完一个用户的时候, 异步给用户授权角色信息。
小伙伴们应该有了解1.8新特性CompletableFuture异步编排吧,具体怎么使用我这里不做深入讲了,api而已。 需要注意点就是要传入自定义线程池,底层默认使用的是 ForkJoinPool.commonPool() 作为其线程池,默认情况下,线程池的大小为可用处理器的数量(即 Runtime.getRuntime().availableProcessors()),在高负载情况下可能会导致性能问题。 需要传入自定义线程池~

	/*** 子线程插入角色信息出错,主线程正常。测试结果:用户信息没插入,角色信息插入了*/@Transactionalpublic void test1() throws ExecutionException, InterruptedException {// 插入用户信息jdbcTemplate.execute("insert into jinbiao_user values (3,'rise3','wang1234..','10086','小程序')");CompletableFuture.runAsync(()->{// 插入角色信息jdbcTemplate.execute("insert into jinbiao_role values (3,'admin')");// 模拟其他业务操作造成任务报错了--throw new RuntimeException("角色信息业务出错啦...");},tulingThreadPoolExecutor).join();  //.get()阻塞等待,则用户信息也不回滚// 处理用户其他逻辑等等....}/*** 主线程出错,子线程插入角色信息正常。测试结果:用户信息没插入,角色信息插入了*/@Transactionalpublic void test2() throws ExecutionException, InterruptedException {// 插入用户信息jdbcTemplate.execute("insert into jinbiao_user values (3,'rise3','wang1234..','10086','小程序')");CompletableFuture.runAsync(()->{// 插入角色信息jdbcTemplate.execute("insert into jinbiao_role values (3,'admin')");},tulingThreadPoolExecutor).get();  throw new RuntimeException("用户信息业务出错啦...");

FAQ:上面代码会存在什么问题?
答:先说结论再说原因,用户信息没插入,角色信息插入了。因为join()方法等待结果抛出的是CompletionExcetion属于运行时异常,所以我们的声明式事务(@Transactional)生效,用户信息是会回滚的。 runAsync里面的任务是非事务方法,所以角色信息会插入成功,不会回滚。
ps:用户信息都不存在,用户的角色信息确已经入库生效了,这怎么行呢。所以我们得想办法:

  • 如果子线程里面插入角色信息报错了,那么它要回滚,插入的用户信息也要回滚。
  • 如果主线程里面插入用户信息报错了,那么它要回滚,插入的角色信息也要回滚

如果计算抛出异常, join() 会抛出一个CompletionException(extends RuntimeException),是运行时异常。
如果计算抛出异常,get() 会抛出一个受检异常 ExecutionException(extends Exception),它包装了实际的异常(也可以通过 getCause() 获取)。 声明式事务(@Transactional)默认不会生效!get() 还会抛出 InterruptedException(extends Exception),如果当前线程在等待结果时被中断,需要显式处理ExecutionException 和 InterruptedException 异常。 属于非运行时异常!声明式事务(@Transactional)默认不会生效!
关于get()/join()阻塞等待获取结果异常测试,rollback指定异常回滚源码分析,@Transactional默认只能回滚运行时异常 以及 所有Error 的 实例。 想具体了解可以去看看我前面文章,有debug关键源码位置:【深入学习Spring声明式事务,测试失效场景及原因分析】

角色信息插入成功。
在这里插入图片描述
用户信息回滚了,未插入。
在这里插入图片描述

三.多线程事务解决方案

之前从其他博主那学过一种基于主事务 +指定几个子事务+结合切面。这种方案, 总体思想就是主事务跟子事务去维护一个共享的事务执行状态,只要有任务报错则把这个共享状态置为true。主线程先执行完则阻塞等待所有子线程执行完,子线程先执行完则阻塞等待主线程执行完,相互等待对方完成,最后根据这个共享状态判断是否需要回滚或者提交。 那位博主的代码很复杂,他demo其实有小问题的然后我改造后测试是ok的,怕给我的小伙伴们劝退了,不贴这种方式了hhh,讲点其他简单又好用的方式。

1. 子线程新开一个事务

让主线程和子线程不在同一个事务里,把线程里面的逻辑放到一个新的事物方法里面去,有异常则各自事物回滚各自的。

todo:还要考虑主线程先报错了,子线程正常的情况。主线程需要通知子线程进行回滚,我们可以使用父子线程传一个布尔值,子线程判断这个布尔值为true,则抛异常~

	@Autowiredprivate RoleService roleService;@Transactionalpublic void test1() throws ExecutionException, InterruptedException {// 插入用户信息jdbcTemplate.execute("insert into jinbiao_user values (3,'rise3','wang1234..','10086','小程序')");CompletableFuture.runAsync(()->{roleService.insertRole2();},tulingThreadPoolExecutor).join();  //.get()阻塞等待,则用户信息也不回滚}// 角色相关的业务类
@Service
public class RoleService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic void insertRole1() {jdbcTemplate.execute("insert into jinbiao_role values (1,'admin')");}@Transactionalpublic void insertRole2() {jdbcTemplate.execute("insert into jinbiao_role values (2,'admin')");throw new RuntimeException("子线程事务方法出错啦...");}
}

测试结果:看到两条回滚信息:“Initiating transaction rollback”,数据都未入库。说明我们让主线程和子线程开启不同的事务是可行的。
在这里插入图片描述
如果是使用get阻塞获取结果呢? 上面提到使用get阻塞获取结果抛出的是ExecutionException(extends Exception) 或者InterruptedException(extends Exception)都是属于非运行时异常,如果我们没有指定rollbackFor异常,这里就需要我们手动try catch 抛出运行时异常了,如这样:

   @Transactionalpublic void test4() throws ExecutionException, InterruptedException {// 插入用户信息jdbcTemplate.execute("insert into jinbiao_user values (3,'rise3','wang1234..','10086','小程序')");try {CompletableFuture.runAsync(()->{roleService.insertRole2();},tulingThreadPoolExecutor).get();  //.get()阻塞等待,则用户信息也不回滚}catch (Exception e){throw new RuntimeException("角色信息业务出错啦...");}}

测试结果:插入角色信息出错,用户信息、角色信息都一起回滚,控制台日志提示两条"Initiating transaction rollback",

2. 使用编程式事务

使用共享变量来标记是否有异常,并在两个线程中各自进行回滚,主线程使用TransactionTemplate开启一个事务,子线程也使用TransactionTemplate开启一个事务,并在执行过程中检查共享变量,决定是否回滚。如果共享变量被设置为异常状态,则在主线程中也触发回滚。。示例代码如下:

public void programmingTransaction() {transactionTemplate.execute(status -> {// 插入用户信息jdbcTemplate.execute("insert into jinbiao_user values (3,'rise3','wang1234..','10086','小程序')");CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {transactionTemplate.execute(innerStatus -> {// 插入角色信息jdbcTemplate.execute("insert into jinbiao_role values (3,'admin')");// 模拟异常throw new RuntimeException("角色信息业务出错啦...");});} catch (Exception e) {// 标记为有错误needRollBack.set(true);}});// 等待子线程完成future.join();// 根据共享变量决定是否回滚if (needRollBack.get()) {// 设置主线程事务回滚status.setRollbackOnly();}return null;});}

测试结果:插入角色信息出错,用户信息、角色信息都一起回滚,控制台日志提示两条"Initiating transaction rollback"
在这里插入图片描述
使用编程式事务还有通过PlatformTransactionManager的方式,实现上与上面类似。只是PlatformTransactionManager来进行编程式事务使用上有区别,需要自己手动commit/rollback事务状态(TransactionStatus), 思想是一样的,不做多介绍啦。

当然还有很多种方式处理多线程事务问题,个人认为上面两种处理方案是比较简单的了,性能也是最优解了。

四.小结

算是最简单的方式来介绍多线程场景下事务失效问题处理方案了。


http://www.mrgr.cn/news/62325.html

相关文章:

  • KNN分类算法 HNUST【数据分析技术】(2025)
  • C语言-结构体数组练习题
  • 子Shell及Shell嵌套模式
  • STM32-笔记12-实现SysTick模拟多线程流水灯
  • Ansible(自动化运维)环境搭建及ansible-vault加密配置
  • 用Unity做没有热更需求的单机游戏是否有必要使用AssetBundle?
  • 从openjdk17 C++源码角度看 java类成员变量是怎么赋值的
  • 理解环境变量与Shell编程:Linux开发的基础
  • DS18B20+测量系统可编程分辨率高精度数字温度传感器芯片
  • Python——石头剪刀布(附源码+多模式二改优化版)
  • C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(6)
  • <HarmonyOS第一课>给应用添加通知和提醒的习题
  • VC2012创建弹出式菜单
  • 智能进阶之路:从基础模型到个性化代理—探索Agent与微调的共生之道
  • [专有网络VPC]创建和管理流量镜像
  • 神奇的数据恢复工具:让丢失的数据重现
  • 线上 Dump
  • 【数据结构】链表详解:数据节点的链接原理
  • 积鼎国产CFD软件VirtualFlow新版上线:新增30余项新功能,多相流仿真效率升级
  • C#与C++交互开发系列(十七):线程安全
  • MyBatis-Plus:简化 CRUD 操作的艺术
  • 「动态规划」1/n:什么是动态规划?
  • 能通过Ping命令访问CentOS 9 Stream,但在使用Xshell连接
  • SQLI LABS | Less-20 POST-Cookie Injections-Uagent field-error based
  • Python酷库之旅-第三方库Pandas(178)
  • MySQL Workbench Data Import Wizard:list index out of range