1.传统的两私有一公开(私有构造方法、私有静态实例(懒实例化/直接实例化)、公开的静态获取方法)涉及线程安全问题(即使有多重检查锁也可以通过反射破坏单例),
2.静态内部类实现。
3.目前最为安全的实现单例的方法是通过内部静态enum的方法来实现,因为JVM会保证enum不能被反射并且构造器方法只执行一次。(《Effective Java》作者推荐)
下面是示例及简要分析。



//下面这个这里来自 https://www.cnblogs.com/rwxwsblog/p/6662951.html
双重检查加锁模式相对于普通的单例和加锁模式而言,从性能和线程安全上来说都有很大的提升和保障。然而双重检查加锁模式也存在一些隐蔽不易被发现的问题。首先我们要明白在JVM创建新的对象时,主要要经过三个步骤。
分配内存
初始化构造器
将对象指向分配的内存地址
这样的顺序在双重加锁模式下是么有问题的,对象在初始化完成之后再把内存地址指向对象。
问题的根源
但是现代的JVM为了追求执行效率会针对字节码(编译器级别)以及指令和内存系统重排序(处理器重排序)进行调优,这样的话就有可能(注意是有可能)导致2和3的顺序是相反的,一旦出现这样的情况问题就来了。
java源代码到最终实际执行的指令序列:

前面的双重检查锁定示例代码的(instance = new Singleton();)创建一个对象。这一行代码可以分解为如下的三行伪代码:
memory = allocate();//1:分配对象的内存空间ctorInstance(memory);//2:初始化对象instance = memory;//3:设置instance指向刚分配的内存地址
上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的,详情见参考文献1的“Out-of-order writes”部分)。2和3之间重排序之后的执行时序如下:
memory = allocate();//1:分配对象的内存空间instance = memory;//3:设置instance指向刚分配的内存地址//注意,此时对象还没有被初始化!ctorInstance(memory);//2:初始化对象



网友评论