单例模式的几个问题(2)-DCL的缺陷

单例模式的几个问题(2)-DCL的缺陷

  DCL模式的单例实现方式一直是各大教科书、老师、博客的推荐实现方式,可是在高并发过程当中仍存在失败的可能性。首先先看一个使用双重检查的单例模式:缓存

//双重检查
public class Singleton4 {

    private static Singleton4 singleton4 = null;

    private Singleton4(){}

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

    public void doSomething(){
        System.out.println(this.getClass().getName());
    }

}

  双检锁机制的出现确实是解决了多线程并行中不会出现重复new对象,并且也实现了懒加载。这样就没问题了吗? 重点来了 ,因为 new Singleton4() 不是一个原子操做,实际执行的时候须要如下几个步骤:多线程

     1.memory=allocate();
     2.ctorInstance(memory); //对象初始化
     3.instance=memory;    //设置instance指向刚被分配的内存;并发

  执行代码时,为了提升性能,编译器和处理器经常会对指令进行重排序。重排序分为三种:编译器重排序,指令级并行重排序,内存系统重排序,实现优化,优化结果多是函数

    1.   初始化 Singleton4 对象;高并发

    2.   把 Singleton4 对象地址赋给instance变量;性能

  也多是这样优化

    1. 初始化一半Singleton4对象;this

    2. 把Singleton4对象地址赋给instance变量;spa

    3. 初始化剩下的Singleton4对象;线程

   若是是第二种状况,在多线程状况下第一个线程已经进入了  singleton4 = new Singleton4(); 正在初始化,此时已经将Singleton4对象的地址赋给instance变量,可是Singleton4对象仍为初始化完毕。第二个线程进入函数 singleton4 == null 为假,获取instance对象并返回,线程若是访问其中某些属性或者方法可能访问到其成员变量的不正确值。具体来讲Singleton.getInstance().getSomeField()有可能返回someField的默认值0。若是程序行为正确的话,这应当是不可能发生的事,由于在构造函数里设置的someField的值不可能为0。

  很显然只要使用jmm的某种限制就可让上面的重排序不会发生,这种重排序限制就是volatile。

  volatile有两种内存语义,一种是缓存一致性,另外一种就是加屏障了。所谓缓存一致性就是当读volatile变量时,jmm会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。至于屏障类型有四种,StoreStore屏障,StoreLoad屏障,LoadLoad屏障,LoadStore屏障。简单来讲,只要volatile变量于普通变量之间的重排序可能破坏volatile的内存语义,这种重排序就会被编译器重排序规则和处理器内存屏障插入策略禁止。

    因为有了volatile的存在,Singleton4的赋值指令不会被优化到new Singleton4()中间去,这就保证了另一个线程若是看到了Singleton4被赋值的时候,其指向的对象必定是被初始化完成的。

相关文章
相关标签/搜索