spring事务加锁不生效,spring事务锁机制
00-1010 1.我的想法。图表的原因。3.解题总结。最近看到一个技术问题:同步锁问题?
启动10,000个线程,每个线程给employee表的money字段加1[初始值为0]。不使用悲观锁和乐观锁,但是在业务层方法中添加了synchronized关键字。问题是,代码执行后,数据库中的money字段小于10,000,而不是10,000。有什么问题?
服务层代码:
SQL代码(没有悲观/乐观锁定):
用1000个线程运行代码:
简单来说:多线程运行一个用synchronized关键字修饰的方法。数据库在方法中操作。按照正常逻辑,最终值应该是1000,但是经过多次测试,结果低于1000。这是为什么呢?
00-1010由于测试结果低于1000,说明这段代码不是线程安全的。它不是线程安全的,那么问题在哪里呢?众所周知,synchronized方法可以保证修改后的代码块和方法的顺序、原子性和可见性。
讲道理,上面的代码运行,问题中的服务层的increaseMoney()是有序的、原子的、可见的,所以应该得出结论,与synchronized无关。
既然在Java层面上找不到原因,那就来分析一下数据库层面(因为方法是对数据库进行操作的)。@ transitional注释添加在increaseMoney()方法之前,表示该方法是事务性的。事务可以保证同一组中的SQL要么同时成功,要么同时失败。讲道理。如果没有错误,每个线程应该为货币值生成1。理论上结果应该是1000。
根据上面的分析,我怀疑提问者没有测试好(hhhh,escape),所以我也跑去测试了一下,发现按照提问者的方式使用确实有问题。
首先发布我的测试代码:
@ RestControllerpublic class employee controller { @ auto wired private employee service employee service;@ request mapping(/add )public void add employee(){ for(int I=0;i 1000i ) {新线程(()- employeeService.addEmployee())。start();} } } @ service public class employee service { @ auto wired private EmployeeRepository EmployeeRepository;@ Transactional Public Synchronized Void Add Employee(){//找出id为8的记录,然后每增加一次年龄Employee repository . get one(8);System.out.println(员工);整数age=employee . get age();employee . setage(1岁);employeeRepository.save(雇员);}}简单打印了一下每次得到的employee值,得到了SQL执行的顺序,如下(贴一小部分):
如下(贴一小部分):
从打印情况可以得出结论,在多线程的情况下,addEmployee()方法不是串行执行的。这就导致了对同一个值的反复修改,所以最终值小于1000。
目录
发现不是同步执行的,所以我怀疑synchronized关键字和Spring之间一定有冲突。于是我根据这两个关键词搜索,发现了问题。
我们知道Spring事务的底层是Spring AOP,Spring AOP的底层是动态代理技术。让我们回顾一下动态代理:
public static void main(string[]args){//target Object Object target;proxy . newproxyinstance(class loader . getsystemclassloader(),Main.class,新调用
Handler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 但凡带有@Transcational注解的方法都会被拦截 // 1... 开启事务 method.invoke(target); // 2... 提交事务 return null; } }); }实际上Spring做的处理跟以上的思路是一样的,我们可以看一下TransactionAspectSupport类中invokeWithinTransaction():
调用方法前开启事务,调用方法后提交事务
在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。
三、解决问题
从上面我们可以发现,问题所在是因为@Transcational注解和synchronized一起使用了,加锁的范围没有包括到整个事务。所以我们可以这样做:
新建一个名叫SynchronizedService类,让其去调用addEmployee()方法,整个代码如下:
@RestControllerpublic class EmployeeController { @Autowired private SynchronizedService synchronizedService ; @RequestMapping("/add") public void addEmployee() { for (int i = 0; i < 1000; i++) { new Thread(() -> synchronizedService.synchronizedAddEmployee()).start(); } }}// 新建的Service类@Servicepublic class SynchronizedService { @Autowired private EmployeeService employeeService ; // 同步 public synchronized void synchronizedAddEmployee() { employeeService.addEmployee(); }}@Servicepublic class EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Transactional public void addEmployee() { // 查出ID为8的记录,然后每次将年龄增加一 Employee employee = employeeRepository.getOne(8); System.out.println(Thread.currentThread().getName() + employee); Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee); }}
我们将synchronized锁的范围包含到整个Spring事务上,这就不会出现线程安全的问题了。在测试的时候,我们可以发现1000个线程跑起来比之前要慢得多,当然我们的数据是正确的:
抛开上面事务造成的synchronized失效问题,synchronized本身是悲观锁,代价偏高,像数据库数据修改的线程安全问题,可以使用乐观锁,在表中添加version字段,每次修改时预期值与数据库值比较,失败的话一定次数自旋尝试修改,修改成功的话version+1。
总结
到此这篇关于Spring事务管理下synchronized锁失效问题的文章就介绍到这了,更多相关Spring事务synchronized锁失效内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。