数据库中存在一张表,用于存储每个调查问卷的填报编号,填报编号连续且唯一,问卷填报后,在最终提交时, 调用service的接口,获取填报编号
@Override
@Transactional
public synchronized String getQuestionAnswerNumber(String code) {
Integer no = genNumberMapper.getQuestionAnswerNoByCode(code);
if(no == null){
genNumberMapper.insertQuestionAnswerNoByCode(code);
no = 0;
}
GenNumber genNumber = new GenNumber();
genNumber.setStub(code);
genNumber.setNo(no + 1);
genNumberMapper.setQuestionAnswerNoByCode(genNumber);
return String.format("%06d", (no + 1));
}
代码编写流程主要是,通过code关键字,获取填号,然后将编号加1存入到数据库,这样下一个人获取编号时,能够按照顺序递增。为了避免多线程情况下,填报编号错乱,使用synchronized关键字,为方法加锁。
然而在生产环境中,并发量大的情况下,导出数据时,填报编号还是重复了。。。。
我们已经在方法生加了锁,为何编号还会出现重复?
分析一:
pring 在使用@Transactional关键字开启事务时,在方法执行完毕前,事务没有提交,数据会缓存在mybatis的一级缓存中,当方法执行完后,数据再进行提交。可我们为方法加了锁,为何还是会产生重复的数据呢,经过推测与测试,可能是获取填报编号的方法已经执行了,但事物并没有提交,这个时候另一个线程已经已经进来,导致获取的数据不准确。
所以这里要打印一下sql,看是否调用数据库。
分析二:
应该是synchronized和@Transaction一起使用导致的。spring中对事务的处理,是采用动态代理的方式,事务里的方法是包含synchronized方法,导致有多个线程进入到事务中。
我们知道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():
Spring事务管理是如何实现的
调用方法前开启事务,调用方法后提交事务
Spring事务和synchronized锁互斥问题
在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。
事务未提交,别的线程读取到旧数据
问题解决:
1.建议是锁上移,也就是说要包住整个事物单元。
2.去掉@Transactional