前提知识点:volatile可以保证可见性+防止指令重排序,synchronized可以保证可见性+防止指令重排序+原子性。
也即是说volatile是synchronized的功能子集,我们知道在【懒汉式-加双重校验锁】的单例模式实现中已经使用了synchronized关键字,那为什么还需要加volatile关键字呢
回顾【懒汉式-加双重校验锁&防止指令重排序的懒汉式】
public class MyManger3 {
private static volatile MyManger3 instance;
private MyManger3() {
}
public static MyManger3 getInstance() {
if(instance==null){ //a
synchronized (MyManger3.class){ //b
if(instance==null){ //c
instance=new MyManger3(); //d
}
}
}
return instance;
}
}
---------------------
作者:明月(Alioo)
来源:CSDN
原文:https://blog.csdn.net/hl_java/article/details/70148622
版权声明:本文为博主原创文章,转载请附上博文链接!
在回答这个问题之前你需要知道的知识点
instance=new MyManger3();
这个语句不是一个原子操作,编译后会多条字节码指令:
- 步骤1.为new出来的对象开辟内存空间
- 步骤2.初始化,执行构造器方法的逻辑代码片段
- 步骤3.完成instance引用的赋值操作,将其指向刚刚开辟的内存地址
可能场景-线程t1,t2均到达 代码b处
这个时候假若线程t1获得锁,t2处于阻塞状态,直到t1 依次执行代码a,b,c,d,并且在释放锁之前会将对变量instance的修改刷新到主存当中,保证当其他线程再进入的时候,在主存中读取到的就是最新的变量内容了。
t1释放锁之后,t2获得锁,根据重新从主内存拿到的变量instance值判断不为null,则直接跳过代码d的执行,即线程2只执行了代码a,b,c就释放掉了锁。
结论:这个场景下线程t1,t2会拿到了一个完整的instance所以是不存在问题的。
真正的问题场景-线程t1执行到代码d处,线程t2执行到代码a处
线程t1执行到代码d处时,在没有加volatile关键字修饰instance时是存在指令重排序的问题的,假若代码d的执行顺序是步骤1、步骤3、步骤2。
在线程t1执行完成步骤3,还没有执行步骤2时,线程t2执行到代码a处,对instance进行判断是否为null,发现不为null则直接返回使用(但此时instance是不一个不为null的但是没有初始化完成的对象)
结论:这个场景下线程t1是没有问题的会得到一个完整的instance,但是t2会提前拿到了一个不完整的instance是存在问题的,所以需要加上volatile来禁止这个语句instance=new MyManger3();
进行指令重排序。
参考文章
https://blog.csdn.net/xiakepan/article/details/52444565
https://www.cnblogs.com/damonhuang/p/5431866.html 这篇文章记得看下评论区
作者相关文章
Singleton单例模式的几种创建方法
Singleton单例模式-如何防止JAVA反射对单例类的攻击?
Singleton单例模式-如何防止序列化对单例类的攻击?
Singleton单例模式-【懒汉式-加双重校验锁&防止指令重排序的懒汉式】实现方案中为什么需要加volatile关键字?