Singleton单例模式-【懒汉式-加双重校验锁&防止指令重排序的懒汉式】实现方案中为什么需要加volatile关键字

前提知识点: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关键字?

发布了100 篇原创文章 · 获赞 64 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/hl_java/article/details/89160086
今日推荐