sychronized底层原理探秘

Synchronized作用:

官方解释:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
一句话总结出Synchronized的作用:
  能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果

synchronized的底层原理及其实现:

代码结合模型分析的更加透彻

场景:模拟双十一秒杀活动,数据库一共有5条数据,使用JMeter模拟30个线程同时访问以下程序,结果是什么


@Service
public class SychronizedTest {



	private  static Object object=new Object();


	public String decStockNoLock(){
		Integer stock ;
		synchronized (object){
		List<Map> result=jdbcTemplate.query("select stock from shop_order where id=10");
		if(result==null||(stock=(Integer)result.get(0).get("stock"))<=0){
			logger.info("下单失败,已经没有库存了");
			return "下单失败,已经没有库存了";
		}
		stock--;
		jdbcTemplate.update("update shop_order set stock=? where stock=1",stock);
		logger.info("下单成功,当前剩余产品数------>"+stock);
		}
		return "下单成功,当前剩余产品数------>"+stock;
	}
}

假设我们没有加synchronized关键字修饰代码块,那么这30个线程都会下单成功。如果加了synchronized后则只有几个线程会下单成功。为什么呢?

三条了逻辑操作语句如下:

1  List<Map> result=jdbcTemplate.query("select stock from shop_order where id=10");
2  stock--;
3  jdbcTemplate.update("update shop_order set stock=? where stock=1",stock);

不加锁:
先上图:

最开始主存里面的stack=5,三个线程获取到了这个stack变量,将他们拷贝到自己的工作内存中,对stack进行stack–操作,此时三个线程将自己工作内存的stack变量通过原子操作store和write同步到主内存.此时bug就发生了,明明三个线程都对stack变量进行了stack–操作,此时的stack应该是2才对。

上述方法不是同步方法,可能线程1执行了第二行逻辑代码,线程2执行了第三行逻辑代码,线程3又执行了第一行逻辑代码,那么就会导致数据的不一致性。

加锁:
给需要同步的代码块加上synchronized关键字就可以避免这一现象,为什么呢?如果是官方给的解释,那等于值告诉我们是什么,没告诉我们为什么。

先从Java内存模型分析:
还是先上图:

首先线程1获取到了Object对象的锁,此时JVM会通过lock原子操作给Object对象加一个锁。其他的线程对这个对象无法读,无法写,其他的线程进行阻塞,进入阻塞队列。直到线程1,做完了按照原子操作的顺序完成操作,释放锁。然后会唤醒其他因为等待这个锁释放而进入阻塞队列的线程。(注意:是等待这个锁而进入阻塞队列的线程,而不是其它阻塞队列的线程)

再从字节码的执行分析:
还是先上图:

我们分析了Java执行的字节码文件,发现在执行逻辑的前面有一个monitorenter在执行逻辑的后面有一个monitorenterexit.如上图,每一个对象在创建的时候,JVM就会给它匹配一个Monitor对象。
解释了monitor对象,我们就来解释moniter的是实现原理:
JVM内置锁通过synchronized的使用,通过内置对象Monitor(监视器锁)实现,基于进入和退出Monitor对象实现方法和代码块同步,监视器的锁的实现依赖操作系统的底层Mutex Lock(互斥锁)来实现,是一个重量级锁,性能较低。
原理如下图:

通过以上2中方法分析,应该已经很清楚了synchronized的底层原理