很是经典的例子,基本上对java有了解的同窗均可以写出来,咱们的例子,可能存在一个BUG,这个BUG的缘由是,JMM出于对效率的考虑,是在happens-before原则内(out-of-order)乱序执行。java
public class LazySingleton { private int id; private static LazySingleton instance; private LazySingleton() { this.id= new Random().nextInt(200)+1; // (1) } public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } } } return instance; // (6) } public int getId() { return id; // (7) } }
咱们初始化一个类,会产生多条汇编指令,然而总结下来,是执行下面三个事情:安全
1.给LazySingleton 的实例分配内存。
2.初始化LazySingleton 的构造器
3.将instance对象指向分配的内存空间(注意到这步instance就非null了)app
Java编译器容许处理器乱序执行(out-of-order),咱们有多是1->2->3也有多是1->3->2。即咱们有可能在先返回instance实例,而后执行构造方法。dom
即:double-check-locking可能存在线程拿到一个没有执行构造方法的对象。性能
线程A、B执行getInstance().getId()优化
在某一时刻,线程A执行到(5),而且初始化顺序为:1->3->2,当执行完将instance对象指向分配空间时。此时线程B执行(1),发现instance!=null,继续执行,最后调用getId()返回0。此时切换到线程B对构造方法初始化。this
利用类第一次使用才加载,加载时同步的特性。
优势是:官方推荐,能够能够保证明现懒汉模式。代码少。
缺点是:第一次加载比较慢,并且多了一个类多了一个文件,总以为不爽。线程
public class SingletonKerriganF { private static class SingletonHolder { static final SingletonKerriganF INSTANCE = new SingletonKerriganF(); } public static SingletonKerriganF getInstance() { return SingletonHolder.INSTANCE; } }
volatile禁止了指令重排序,因此确保了初始化顺序必定是1->2->3,因此也就不存在拿到未初始化的对象引用的状况。
优势:保持了DCL,比较简单
肯定:volatile这个关键字多少会带来一些性能影响吧。code
public class Singleton(){ private volatile static Singleton singleton; private Sington(){}; public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
经过一个temp,来肯定初始化结束后其余线程才能得到引用。
同时注意,JIT可能对这一部分优化,咱们必须阻止JTL这部分的"优化"。对象
缺点是有点难理解,优势是:能够不用volatile关键字,又能够用DLC,岂不妙哉。
public class Singleton { private static Singleton singleton; // 这类没有volatile关键字 private Singleton() { } public static Singleton getInstance() { // 双重检查加锁 if (singleton == null) { synchronized (Singleton.class) { // 延迟实例化,须要时才建立 if (singleton == null) { Singleton temp = null; try { temp = new Singleton(); } catch (Exception e) { } if (temp != null) //为何要作这个看似无用的操做,由于这一步是为了让虚拟机执行到这一步的时会才对singleton赋值,虚拟机执行到这里的时候,必然已经完成类实例的初始化。因此这种写法的DCL是安全的。因为try的存在,虚拟机没法优化temp是否为null singleton = temp; } } } return singleton; } }