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的底层原理