@Transactional和synchronized同时使用时的一些问题以及解决

@Transactional和synchronized同时使用并不能保证事务一致性

背景
任何事情都有一个发生背景
有个需求【一个业务里面包含多个事务,而且还需要避免其他线程的影响,所幸的是该服务只需要启动单实例,不然还要考虑分布式的影响】
我的思路就是用@Transactional 和 synchronized来保证事务一致性和多线程影响,结果发现并没有如愿
分析原因
    @Transactional
    public ResultVo service(){
        synchronized (LOCK){
            //doservice
        }
    }
关于为什么不是用synchronized 关键字而是使用代码块锁是为了不影响其他方法,关键字默认锁的是当前类对象
一开始我的代码是这样的,乍一看好像没什么问题,但是为什么会出问题呢
排查问题
问题重现 : 一定要重现问题,任何重现不了的问题都不是问题,任何存在的问题都必能重现
由近到远 : 先确认自己的代码没问题,再考虑外部代码(如二方库,三方库)
从内到外 : 程序本质就是IPO,包含输入(input),程序(program)/指令集,输出(output),先确认输入没有问题,再确认代码逻辑
由浅入深 : 从易到难,从上到下,先上层API,http传输等,再底层API,源码,jvm等

说到这里,问题就比较容易分析了

首先我的输入没有问题

其次我逻辑代码也没有问题

接下来就是二方库和三方库了

由于事务用的是spring的事务,是基于aop实现的,ok找到问题了

由于spring的aop,会在@Transactional修饰的方法之前开启事务,之后再加锁,当锁住的代码执行完成后,在提交事务,
因此synchronized代码块执行是在事务之内执行的,可以推断在代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的库存数据不是最新的。
解决问题
在网上看到很多解决方案都在说在外层套一个方法,把锁的级别提高,或则说在controller加锁
这样可能会导致事务不会回滚
spring事务管理中,使用Synchronized修饰事务方法,同步为什么会失效
https://blog.csdn.net/weixin_54401017/article/details/129768305
这里提供一个解决方案,利用线程池,当 然业务代码还是要加@Transactional的!!!
private ExecutorService executorService = null;//线程池

public ResultVo updateOk(@RequestBody OtcTransferOutManageVo otcTransferOutManageVo){
        //如果线程池为null或则线程池被关闭了,创建一个单线程化线程池
        if (executorService == null || executorService.isShutdown()) {
            executorService = Executors.newSingleThreadExecutor();
        }
        if (otcTransferOutManageVo.getId() == null) {
            return ResultVoBuildUtils.buildResultVo( Constants.FAIL, "参数错误" );
        }
        //使用submit执行业务  Future和result.get()是为了保障线程同步,不然变成异步线程是无法捕获异常信息的
        Future<ResultVo> result = executorService.submit(()->{
            ResultVo resultVo = otcTransferOutManageService.updateOkStatus(otcTransferOutManageVo);
            LoggerHepler.writeInfoLog( TransferInController.class, resultVo.getMsg() );
            return resultVo;
        });
         executorService.shutdown();
        try {
            return result.get();
        } catch (Exception e) {
            LoggerHepler.writeErrorLog( TransferOutManageController.class, ServiceTypeENUM.ASSET_MANAGEMEN, BusinessTypeENUM.TRADE,
                   ExceptionCodeConstants.UPDATE_MYSQL_EXCEPTION, "updateOk error!", e );
            return ResultVoBuildUtils.buildFaildResultVo();
        }
    }
关于这段代码有几点需要说一下

newSingleThreadExecutor()创建单线程化的线程池

通过源码可以看到 :
该方法创建一个单一工作线程的线程池,如果此线程在执行过程中失败了,会有一个新的线程来继续完成未完成的工作
任务会被保证是顺序执行的(串行),并且再任意时间都不会超过一个活跃线程
这里基于的是LinkedBlockingQueue,这是一个线程安全的阻塞队列

shutdown()方法

在调用这个方法后,会在submit的任务执行完成后将线程池变为shutdown状态,拒绝新的任务(线程池不会立刻退出,直到任务完成)
如果已经关闭了,调用此方法也不会有额外的影响
此时不能再往线程池中添加新任务,否则会抛出RejectedExecutionException异常。
  • Future && get()

因为这里需要获取线程执行的返回值,
无论是继承Thread类还是实现Runnable接口都无法获取到线程执行的返回值(默认是异步线程)
所以这里用到的是线程的第三种创建方式,实现callable接口重写call方法,当然重写call方法被我用lambda表达式隐含了
所以get()就是为了获取线程执行的返回值

submit()方法

传入一个Callable 任务,返回执行完成的返回值

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Transactional和synchronized是实现事务控制和同步的两种机制。 @Transactional是Spring框架提供的注解,用于实现事务控制。它通过AOP(面向切面编程)实现,在方法执行完成后才提交事务。而synchronized是Java关键字,用于实现同步。它可以确保同一个对象的同步方法或代码块在同一间只能被一个线程执行,以避免多线程并发访问的数据竞争问题。 然而,这两个机制不能一起使用,因为@Transactional注解事务是通过AOP实现的,而synchronized是通过锁机制实现的。当@Transactional注解的方法执行完成后才提交事务,而synchronized代码块是在一个事务内执行的。因此,如果在一个方法中同使用@Transactionalsynchronized,会出现第一个线程释放锁后但是事务还未提交,第二个线程就进入同步代码块获取到未提交的数据库数据的情况。这样可能会导致数据一致性的问题。所以,在使用事务控制和同步的候,需要注意避免同使用@Transactionalsynchronized。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [关于@Transactionalsynchronized使用问题](https://blog.csdn.net/YXXXYX/article/details/127325870)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值