松花皮蛋的黑板报
  • 分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的

扫一扫
关注公众号

Synchronized 同步出现失效

博客首页文章列表 松花皮蛋me 2019-12-21 20:49

Synchronized ,大家都知道这个是Java 提供的一种原子性内置锁,其实现原理是通过获取对象的监视器monitor进行来实现同步的,只有当线程获取到对象monitor才能继续执行,否则该线程进行阻塞(等待)。
示例:

public class DemoServiceImpl { 

	@Autowired    
	private DemoDao demoDao;    

	// 根据id获取序列号,获取完之后进行 + 1        
	@Transactional    
	public synchronized String getSn(Long id) {
	       DemoSn sn = demoDao.getSnById(id);       
	       sn.setSn(sn.getSn() + 1);       
	       demoDao.update(sn);         
	       return sn.getSn().toString();    
	}    

}


描述一下代码示例: 该方法涉及++ 1 ,当如果多个线程进行调用该方法时,会出现数据安全性问题,所以我在这里进行加了 Synchronized, 保证该方法的每次执行只能允许一个线程进来。

本以为这里加了 Synchronized, 就是线程安全了,但是经过测试之后发现,这里同步锁并没有起作用,多个线程进行调用该方法时,还是出现返回的值重复。


先简单介绍一下Spring @Transactional执行原理: @Transactional 注解是Spring 提供来进行控制事务的注解,当注解标明在方法上时, 则会对 该方法进行做AOP 增强(动态代理),然后在方法执行前,开启事务,执行后提交事务。

如果上述示例方法只添加了 Synchronized, 并没有标明@Transactional, 因为加了同步锁,所以getSn() 一次只能允许一个线程进入, 执行过程:

A(线程): DemoServiceImpl#getSn() > B(线程): DemoServiceImpl#getSn()

如果上述示例方法添加了 Synchronized,并标明@Transactional 线程的执行过程:

A(线程): Spring begins transactional > DemoServiceImpl#getSn() > Spring commits transactional


B(线程): Spring begins transactional > DemoServiceImpl#getSn() > Spring commits transactional


这里看着是没有问题,开启事务 -> 执行方法体 -> 提交事务,但是这里也恰恰是出现Synchronized 失效的关键。

Synchronized 失效关键原因:是因为Synchronized锁定的是当前调用方法对象,而Spring AOP 处理事务会进行生成一个代理对象,并在代理对象执行方法前的事务开启,方法执行完的事务提交,所以说,事务的开启和提交并不是在 Synchronized 锁定的范围内。出现同步锁失效的原因是:当A(线程) 执行完getSn()方法,会进行释放同步锁,去做提交事务,但在A(线程)还没有提交完事务之前,B(线程)进行执行getSn() 方法,执行完毕之后和A(线程)一起提交事务, 这时候就会出现线程安全问题。