当前位置:   article > 正文

Synchronized锁在Spring事务管理下,为啥还线程不安全

Synchronized锁在Spring事务管理下,为啥还线程不安全

既然Java层面上找不到原因,那分析一下数据库层面的吧(因为方法内操作的是数据库)。在increaseMoney()方法前加了@Transcational注解,说明这个方法是带有事务的。事务能保证同组的SQL要么同时成功,要么同时失败。讲道理,如果没有报错的话,应该每个线程都对money值进行+1。从理论上来说,结果应该是1000的才对。

(参考我之前写过的Spring事务:一文带你看懂Spring事务!)

根据上面的分析,我怀疑是提问者没测试好(hhhh,逃),于是我也跑去测试了一下,发现是以提问者的方式来使用是真的有问题

首先贴一下我的测试代码:

@RestController

public class EmployeeController {

@Autowired

private EmployeeService employeeService;

@RequestMapping(“/add”)

public void addEmployee() {

for (int i = 0; i < 1000; i++) {

new Thread(() -> employeeService.addEmployee()).start();

}

}

}

@Service

public class EmployeeService {

@Autowired

private EmployeeRepository employeeRepository;

@Transactional

public synchronized void addEmployee() {

// 查出ID为8的记录,然后每次将年龄增加一

Employee employee = employeeRepository.getOne(8);

System.out.println(employee);

Integer age = employee.getAge();

employee.setAge(age + 1);

employeeRepository.save(employee);

}

}

简单地打印了每次拿到的employee值,并且拿到了SQL执行的顺序,如下(贴出小部分):

搞不懂,Synchronized锁在Spring事务管理下,为啥还线程不安全?

从打印的情况我们可以得出:多线程情况下并没有串行执行addEmployee()方法。这就导致对同一个值做重复的修改,所以最终的数值比1000要少。

二、图解出现的原因

=========

发现并不是同步执行的,于是我就怀疑synchronized关键字和Spring肯定有点冲突。于是根据这两个关键字搜了一下,找到了问题所在。

我们知道Spring事务的底层是Spring AOP,而Spring AOP的底层是动态代理技术。跟大家一起回顾一下动态代理:

public static void main(String[] args) {

// 目标对象

Object target ;

Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 但凡带有@Transcational注解的方法都会被拦截

// 1… 开启事务

method.invoke(target);

// 2… 提交事务

return null;

}

});

}

(详细请参考我之前写过的动态代理:给女朋友讲解什么是代理模式)

实际上Spring做的处理跟以上的思路是一样的,我们可以看一下TransactionAspectSupport类中invokeWithinTransaction():

搞不懂,Synchronized锁在Spring事务管理下,为啥还线程不安全?

调用方法开启事务,调用方法提交事务

搞不懂,Synchronized锁在Spring事务管理下,为啥还线程不安全?

在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。

搞不懂,Synchronized锁在Spring事务管理下,为啥还线程不安全?

三、解决问题

======

从上面我们可以发现,问题所在是因为@Transcational注解和synchronized一起使用了,加锁的范围没有包括到整个事务。所以我们可以这样做:

新建一个名叫SynchronizedService类,让其去调用addEmployee()方法,整个代码如下:

@RestController

public class EmployeeController {

@Autowired

private SynchronizedService synchronizedService ;

@RequestMapping(“/add”)

public void addEmployee() {

for (int i = 0; i < 1000; i++) {

new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();

}

}

}

// 新建的Service类

@Service

public class SynchronizedService {

@Autowired

private EmployeeService employeeService ;

// 同步

public synchronized void synchronizedAddEmployee() {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
897)]

Mybatis面试专题

[外链图片转存中…(img-PWPHH5lS-1713432913897)]

MySQL面试专题

[外链图片转存中…(img-XXCVPJwS-1713432913898)]

并发编程面试专题

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/620967
推荐阅读
相关标签
  

闽ICP备14008679号