@Transactional 中使用线程锁导致了锁失效,震惊我一整年!

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

65001b72c342ba3e12e3662849dc8d49.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:juejin.cn/post/
7311603432925102095

35ddf0cba5f40f32793cff7f6995ddc0.jpeg


一、引出问题

很多小伙伴使用Spring事务时,为了省事都喜欢使用@Transactional。但是@Transactional配合锁,会导致一些预期之外的问题!

在此举例说明。

1、数据准备

我们将在该表中,实现level数据递减的并发操作。

c155ca123d1166eee673afb3005be847.png

Controller中,简单模拟10个线程各自执行10次:

5373e7806488d06b981ffbbc9e06fb2e.png

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

二、@Transactional是如何导致锁失效的

1、不加锁

// service代码
public void test() {
    // 简单的select + update 模拟业务场景
    Model model = mapper.choseOne("99");

 // 实现 level -- 操作
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}

执行结果:我们发现,level只扣减了26,说明存在并发问题!

89ebe58afb565b2878d4bb3ac64e759f.png

2、使用锁

// service代码
private Lock lock = new ReentrantLock();

public void test() {
 try {
     //加锁
     lock.lock();
     // 简单的select + update 模拟业务场景
     Model model = mapper.choseOne("99");
 
  // 实现 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
 } finally {
       lock.unlock(); // 解锁
    }
}

执行结果:我们发现,使用锁是可以控制并发问题。

ba9383933b65350b094a64d0ad9a9424.png

3、使用锁+@Transactional

// service代码
private Lock lock = new ReentrantLock();

@Transactional
public void test() {
 try {
     //加锁
     lock.lock();
     // 简单的select + update 模拟业务场景
     Model model = mapper.choseOne("99");
 
  // 实现 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
 } finally {
       lock.unlock(); // 解锁
    }
}

执行结果:我们发现,level只扣减了86!用了@Transactional之后,锁怎么就失效了呢!

7282d66b4fbf26684a6f06c040781345.png

4、问题分析

我们都知道,@Transactional是通过使用AOP,在目标方法执行前后进行事务的开启和提交。所以,Lock锁住的代码,其实并没有包含住一整个事务!

通过下面的图理解一下:

d2aaeabc60b1ce6231e9d7fecac381ee.png

当线程A将level设置为99时,此时锁已经释放了,但是事务还没提交!!线程B此时可以获取到锁并进行查询,查询出来的level还是线程A修改之前的100,所以出现了并发问题。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

三、解决方案

1、@Transactional单独一个方法

private Lock lock = new ReentrantLock();
@Transactional
public void test1() {
    // 简单的select + update 模拟业务场景
    Model model = mapper.choseOne("99");

 // 实现 level -- 操作
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}

@Autowired
@Lazy
private CommonService commonService;
public void test() {
    try {
        // 加锁
        lock.lock();
        // 自己注入自己,以使用到其代理类
        commonService.test1();
    } finally {
        lock.unlock(); // 解锁
    }
}

执行结果:没有并发问题出现!

fa399be6b0f4a16d6b935b03c6fc9d69.png

或者直接在controller层加锁,也是一样的道理。

2、使用编程式事务

// service代码
private Lock lock = new ReentrantLock();
@Autowired
private PlatformTransactionManager transactionManager;
public void test() {
 try {
     //加锁
     lock.lock();
     // 编程式事务
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        
     // 简单的select + update 模拟业务场景
     Model model = mapper.choseOne("99");
 
  // 实现 level -- 操作
     Model updater = new Model();
     updater.setId("99");
     updater.setLevel(model.getLevel() - 1);
     mapper.updateOne(updater);
     
  // 在锁中提交
        transactionManager.commit(status);
 } finally {
       lock.unlock(); // 解锁
    }
}

执行结果:我们发现,将整个事务都锁住,就没问题了!

db6ffc3612105d622519da3e84907c45.png

欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

be5c6f83814a3fd43ea33ec54820be8a.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

b69cad1073db0a3910c2aec8e40c2295.png

200dafe10186e99260187700554083cc.png3da87ec7693e6ab742a9bb87d0e7b53b.png9dc38cdb4c021dfeed5a73595e518186.png4329474ba1369ce52a8612e443dbefdd.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值