前言
之前的系列文章已经把AOP说的挺明白了,现在我们来说一些日常的可能在用到spring的时候会遇到的一些奇怪的问题,而这类问题的问题就出现在在AOP这块。
在使用Spring事务的时候,如果在方法上加上synchronized锁依然会导致线程是非安全的问题。可能有点说的不明白,我们直接贴代码吧。
- 测试代码:
@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);
}
}
描述一下代码业务吧:开启1000个线程,每个线程的工作是去查询一下员工的数据,然后把员工的年龄数据进行加1的操作,并且进行保存。事先申明在数据库层面并没有用到悲观锁或者乐观锁。当然根据上面的代码(已经在方法上加了锁),按理说最终的结果该员工的年龄应该是1000。
代码贴完,并且也把业务说明清楚了,我们来看一下控制台打印情况:
SQL打印情况.png
看到什么了么?SQL执行的情况并非是我们想象中的串行进行的。这就会导致对同一个值进行重复修改,那么必然的最终的员工的年龄一定是小于1000的。那么问题来了,为什么我已经在方法上加了synchronized关键字了,结果方法依然没有出现串行呢?
-
描述完毕,现在进行解答:
首先先说一下,这个问题好像是事务与synchronized锁的问题,但是我们要知道一点,Spring实现事务的机制是通过SpringAOP来完成的(后面会发一篇文章来描述一下Spring事务的实现机制)。我说的这么明白,那么大家也应该明白了,问题的原因肯定是不会出现在synchronized锁上了,对,就是出现在springAOP的实现机制上。
我们知道在IOC容器初始化bean的时候,在初始化完bean后,会通过BeanPostProcessor接口里的方法来将目标对象替换成代理类对象,而代理类是已经把目标对象里的方法进行了增强处理(可以去看一下之前写的关于AOP的文章)
而如何增强的呢?我们以JDK动态代理为例子,通过实现InvocationHandler接口的invoke方法来实现增强的,我们来看一下Spring实现事务这块是如何实现相关invoke方法的:
Spring事务实现invoke方法部分源码.png
在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题
再聊的深一点:其实这个问题的本质是要对这个事务方法进行串行化处理(不知道大家能不能理解,如果可以理解,那就说明是真的懂了)
处理方法有俩种:
1.去掉synchronized关键字,直接在事务注解上加上配置,让数据库的隔离等级提升到serializable串行化。(数据层层面的串行化)
- 由上面的分析可以知道,看似synchronized关键字锁定了整个addEmployee方法,但是其实它只是锁了一部分代码的代码块而已,并没有整个锁住这个事务方法,我们可以在外层套个壳子,然后锁住这个壳子就行了:(代码层面的串行化)
新建一个名叫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() {
employeeService.addEmployee();
}
}
@Service
public 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);
}
}
在AOP中进行增强俩个方法(该俩方法在一个类里),如果其中一个方法里面调用了另一个方法,那么就会导致一个问题。该方法里调用的另一个方法并没有得到我们预想中的增强处理,这又是怎么回事呢?
暂时先写到这,后面会把这块给补上的......
网友评论