java并发编程之 service层处理并发事务加锁可能会无效
java并发编程之 service层处理并发事务加锁可能会无效
最近注意到一个问题--在service层处理要多次操作数据库事务时往往要@Transactional事务注解,这个时候就要注意了,如果是在并发情况下,而且在service层加了锁,这个时候并不能保证这个事务操作的原子性,并会出现我们意向不到的问题。
本人做了一个测试,service层有一个方法,方法中获取数据库中的一个int值,然后将这个值自增后存入数据库,例如下面两段代码:(注意方法上加了@Transactional事务注解)
@Service
public class TestService {
@Autowired
private TestMapper testMapper;
@Transactional
public synchronized void test() {
int i=testMapper.get();
testMapper.add(++i);
}
}
获下方的写法,都是有问题的。
@Service
public class TestService {
private static final Object obj = new Object();
@Autowired
private TestMapper testMapper;
@Transactional
public void test() {
synchronized (obj) {
int i=testMapper.get();
testMapper.add(++i);
}
}
}
为了解决并发问题上述两段代码都用了不同的形式加了锁,可能大部分初级程序员都会觉得上述代码没有什么问题。但实际是两段代码都有问题。测试的时候用的是jmeter工具,用2000个线程同时访问模拟并发,经过多次测试后数据库中的值都小于2000。
众所周知,我们在spring中使用@Transactional事务注解,那么这个事务的开启和提交是spring利用aop帮我们自动完成的。说白了就是执行方法之前spring帮我们开启事务,方法执行完毕后spring再帮我们把事务提交。而方法的执行和事务的开启及提交并不是一个原子操作。所以方法执行完毕以后事务并没有提交。所以我们无论是将synchronized关键字加再方法体上还是用代码块的方式,只能保证方法中的代码或synchronized代码块中的代码执行的原子性。所以在高并发的情况下,就极有可能出现一些线程service方法已经执行完毕或synchronized代码块已经执行完,但是事务还没有提交(数据库中的值并没有改变),另一些线程就开始读取数据库中的值(没有提交事务之前的值)那么就有可能多个线程读取的是同一个值。所以就会出现和我们预想的结果不一致的情况。所以在并发程序中,而且还牵扯到事务的情况下,要特别注意这一点。
- 在controller层加锁。
- 在service层自己定义事务的开启和提交。