美文网首页技术干货java进阶干货
单例模式双重校验写法及延伸

单例模式双重校验写法及延伸

作者: stone305585 | 来源:发表于2017-06-22 16:33 被阅读0次

最近看一些Java内存模型方面的书,讲了一下Java的对象的内存分配过程,其中有个例子讲解多线程锁的问题,说了下面的例子:

单例写法 双重校验写法

//------------------------双重校验锁------------------
    private static Singleton singleton2;//-------1

    public static Singleton getInstance4() {//------------2
        if (singleton2 == null) {//-----------------------3
            synchronized (Singleton.class) {//------------4
                if (singleton2 == null)//-----------------5
                    singleton2 = new Singleton();//-------6
            }
        }
        return singleton;
    }

问题处在了第6步,Java创建对象的第6步可以分为以下三步:

memory = allocate();//----1
ctorInstance(memory);//-2
instance = memory;//-----3

其中2,3步在JVM编译优化时可能发生重排序,这和采用的JIT有关,并且该重排序遵循intra-thread semantics法则(重排序后不会影响单线程的执行结果)。

如果发生重排序,第3步先于第2步执行,那么A线程可能只是让对象指向内存地址,并没有实质的初始化对象,那么线程B调用时就会发生错误。

解决方案

  • 采用volatile

在Java1.5以后,volatile关键字被加强,这种重排序不允许在多线程中发生。
即在对象声明加上volatile关键字。
实质:禁止编译的重排序。

  • 采用JVM初始化类时加锁

JVM的类的初始化阶段,会获取锁,该锁可以同步多线程对一个类的初始化。
此时衍生一种称为:Initialization On Demand Holder idiom的解决方案。

    //-----------------------------静态内部类---------------
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance3() {
        return SingletonHolder.INSTANCE;
    }
JVM在多线程中初始化对象过程.jpg

** 实质: **利用JVM的多线程初始化对象的特性,允许重排序,但对其他线程不可见。

另外根据Java语言规范,一个类在一下5种情况会发生初始化:
  • T是一个类,并且T的实例被创建。
  • T是一个类,且T中的静态方法被调用
  • T是一个类,且T中的一个静态字段被赋值。
  • T是一个类,且T中的非常量字段被使用
  • T是一个顶级类(TOP Level Class),有断言语句嵌套在T内部被执行。(assert语句,很少用改规则)

相关文章

网友评论

    本文标题:单例模式双重校验写法及延伸

    本文链接:https://www.haomeiwen.com/subject/czefcxtx.html