DCL单例(双重检查单例)是否需要加volatile?为什么?

DCL(双重检查锁定)单例模式在多线程环境下需要使用volatile关键字,以确保对象创建的正确性。volatile保证了线程间可见性和禁止指令重排序,防止多个线程在对象未完全初始化时获取到不完整的实例,从而避免错误的发生。此外,文章提到了《Effective Java》中推荐使用枚举类型实现单例,但实际应用中会根据项目需求选择不同的实现方式。

 答案是肯定的

 要说明具体原因,需要先了解以下几点。

 一.对象的创建过程:

以Object的创建作为例子,创建对象一共五个步骤:

  1. new:分配内存。
  2. dup:复制一份刚刚创建的内存空间的引用,并压栈。(此时栈中有两个内存空间的引用)。
  3. invokespecial:取出一个引用,调用init方法,这里就指构造函数,到这一步,完成了对象的new Object()过程。
  4. astore_1:将栈顶的引用的值赋值给局部变量表角标为1的变量。当前是main方法,局部变量有两个,角标为0的是args,角标为1的是object。这一步是完成对象的初始化。
  5. return:Return void from method。

 二.volatile的作用:

  1. 保证线程的可见性:本质上是使用了CPU的缓存一致性协议。缓存一致性协议有很多,比如MESI、MOSI、MSI等。inter的cpu使用的MESI(M:Modified、E:Exclusive、S:Shared、I:Invalid )。
  2. 禁止指令重排序:使用了内存屏障。在JVM层,volatile修饰的变量在进行写操作前加了StoreStoreBarrier,之后加了StoreLoadBarrier;读操作前加了LoadLoadBarrier,之后加了LoadStoreBarrier。在硬件层面,Windows是通过lock指令实现的。

 三.为什么DCL单例需要加volatile:

public class SingletonDoubleCheckTest {

    private static volatile SingletonDoubleCheckTest INSTANCE;

    private SingletonDoubleCheckTest() {
    }

    public static SingletonDoubleCheckTest getInstance() {
        if (null == INSTANCE) {
            synchronized (SingletonDoubleCheckTest.class) {
                if (null == INSTANCE) {
                    INSTANCE = new SingletonDoubleCheckTest();
                }
            }
        }
        return INSTANCE;
    }
}

     多个线程执行getInstance()方法,第一个线程获得锁,在执行创建对象的过程中,invokespecialastore_<n>进行了指令重排序(根据hanppens-before原则,上面这两条指令是有可能进行指令重排序的。),第一个线程执行完astore_<n>时,此时INSTANCE已经不为空了,有一个引用指向了它,只不过这块内存的内容是NULL,此时第二个线程执行方法,在判断第一个null == INSTANCE,因为INSTANCE已经有地址了,第二个线程不会再继续争抢锁,直接将这个对象返回,这样会出现错误,因为对象并没有创建完成。

     如果加了volatile,就会保证先进行初始化,再赋值给变量。

 总结:在《Effective Java 第三版》中说道:实现单例的一种方法是声明一个包含单个元素的枚举类型,因为即便是在面对复杂的序列化或者反射攻击的时候,也可以防止多次实例化。我个人还没有在项目中使用过枚举类型创建单例,我比较常用的是懒汉式的,具体,只能是因项目而异了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值