记一次并发问题 Synchronized 失效
场景:为避免信息提交重复,给事务方法增加了synchronized修饰符,实际场景中仍然无法完全避免重复,原因是因为在第一个线程执行完synchronized代码段后,此时spring还未完成事务提交,但是其他线程已经进入该代码段,导致信息提交重复。
这里是部分spring aop 实现声明式事务的代码:
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
// 这里执行完成我们被代理的方法以后资源就已经被释放掉了,
// 导致后面的线程可以获得这个锁资源,可以执行方法中的临界区代码
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交事务,也就是说这里的事务还没有提交,但是后面过来的线程已经在查询数据库了,
// 所以查询到的数据还是false
commitTransactionAfterReturning(txInfo);
return retVal;
}
在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。
方案1 很简单 那就是不开事务就行了,再这个方法上不加事务就行 因为 Synchronized 可以保证线程安全。
这个方案的意思就是说不要再同一个方法上用@Transaction 和 Synchronized 例子图就没有贴了 就像我前面的 把注解去掉就好了 (但是前提你这个方案确定是不需要事务)
方案2 再这个里面再调用一层service 让那个方法提交事务,这样的话加上Synchronized 也能保证线程安全。或者直接在controller层加上锁,就可以保证整个方法的原子性了;
Synchronized 失效关键原因:是因为Synchronized锁定的是当前调用方法对象,而Spring AOP 处理事务会进行生成一个代理对象,并在代理对象执行方法前的事务开启,方法执行完的事务提交,所以说,事务的开启和提交并不是在 Synchronized 锁定的范围内。出现同步锁失效的原因是:当A(线程) 执行完insertSelective()方法,会进行释放同步锁,去做提交事务,但在A(线程)还没有提交完事务之前,B(线程)进行执行findOrder() 方法,执行完毕之后和A(线程)一起提交事务, 这时候就会出现线程。