public class Singleton { private volatile static Singleton singleton; //1:volatile修饰 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { //2:减小不要同步,优化性能 synchronized (Singleton.class) { // 3:同步,线程安全 if (singleton == null) { singleton = new Singleton(); //4:建立singleton 对象 } } } return singleton; } } 复制代码
推荐理由:安全
getSingleton
,才会初始化signleton
实例。singleton
是否初始化,减小没必要要的同步开销。singleton
使用volatile
修饰。为何要使用volatile修饰?性能优化
虽然已经使用synchronized进行同步,但在第4步建立对象时,会有下面的伪代码:bash
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
复制代码
当线程A在执行上面伪代码时,2和3可能会发生重排序,由于重排序并不影响运行结果,还能够提高性能,因此JVM是容许的。若是此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton
方法,在判断singleton==null
时不为null
,则返回singleton
。但此时singleton
并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的二、3的重排序在多线程中将被禁止!markdown
public class Singleton { private Singleton(){ } public static Singleton getSingleton(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } } 复制代码
推荐理由:多线程
getSingleton
才初始化Singleton
对象。如何实现线程安全?性能
线程A和线程B同时试图得到Singleton对象的初始化锁,假设线程A获取到了,那么线程B一直等待初始化锁。线程A执行类初始化,就算双重检查模式中伪代码发生了重排序,也不会影响线程A的初始化结果。初始化完后,释放锁。线程B得到初始化锁,发现Singleton对象已经初始化完毕,释放锁,不进行初始化,得到Singleton对象。优化
在涉及到反射和序列化的单例中,建议使用下文的枚举类型模式。spa
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 复制代码
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } 复制代码
饿汉模式的线程安全一样经过类加载解决同步问题,但没有达到懒加载目的。(这里很是感谢之初z-chu
的指正)线程
public enum Singleton { INSTANCE; public void doSomething(){ //todo doSomething } } 复制代码
在Joshua Bloch大神的《Effective Java》是推荐该方法的。虽然线程安全,在实际开发中,尚未被普遍采用。由于太过简洁以至于可读性较差,尚未在实战中被普遍推广。枚举单例模式的线程安全一样利用静态内部类中讲到类初始化锁。枚举单例模式可以在序列化和反射中保证明例的惟一性。code
高手之间的过招,必选择枚举单例模式。