目前比较常见的有4种(DCL为懒汉模式的线程安全版本)。
单例模式的实现一般需要满足以下条件:
1.构造方法私有化,实例属性私有化。
2.必须仅在类的内部完成实例的初始化过程。
3.提供公共静态方法,用以返回已经初始化完成的实例。
4.不可通过反射,反序列化方式获得新的实例。
1.饿汉模式:进行类初始化时就完成实例初始化的方式。可类比月光族,有钱就买。用不用不知道。
优势:由于初始化较早,所以相对于其他模式节省了这部分时间,效率较高。且多线程环境中保证安全运行。
劣势:在当前单例一直未被使用的场景下,资源利用率变的很低,另外无法从外界传递参数用来实例化。
1 /** 2 * @author txba6868 3 * @title: SingleTonTest 4 * @projectName javaAnalysis 5 * @date 2020/1/8 11:08 6 * @desc 单例模式 7 */ 8 public class SingleTonTest { 9 10 //饿汉式:以空间换时间,类初始化时完成 11 private static SingleTonTest singleTonTest = new SingleTonTest(); 12 13 public static SingleTonTest getInstance() { 14 return singleTonTest; 15 } 16 }
2.懒汉模式:有使用需求时通过调用getInstance()方法实现实例初始化。
优势:按需初始化,有效提高资源利用率。第一次调用可动态传参实例化。
劣势:初始化时出现时间成本,在多线程环境下有较高可能出现安全问题,造成多次实例化。
1 /** 2 * @author txba6868 3 * @title: SingleTonTest 4 * @projectName javaAnalysis 5 * @date 2020/1/8 11:08 6 * @desc 单例模式 7 */ 8 public class SingleTonTest { 9 10 //懒汉式:以时间换空间,存在多线程安全问题 11 private static SingleTonTest singleTonTest = null; 12 13 public static SingleTonTest getInstance() { 14 if (singleTonTest == null) { 15 singleTonTest = new SingleTonTest(); 16 } 17 return singleTonTest; 18 } 19 }
3.DCL(doule check locking)主要是为了解决懒汉模式带来的线程安全问题做出的优化。但DCL仍旧存在安全问题,主要是因为程序中的“new”操作并非原子操作,代码注释中已写明,涉及到指令重排序及可见性问题,在java1.5之后JMM提供了volatile关键字,通过内存屏障的方式解决了该问题(网上有言论称volatile并未完美解决,仍然存在安全问题,还待博主继续探索下)。并且DCL由于内存屏障的原因,有可能屏蔽一些效率较高的代码优化,反而会引起效率的下降。
1 /** 2 * @author txba6868 3 * @title: SingleTonTest 4 * @projectName javaAnalysis 5 * @date 2020/1/8 11:08 6 * @desc 单例模式 7 */ 8 public class SingleTonTest { 9 10 //构造方法私有化(略) 11 //DCL模式:同样存在线程安全问题,因为"new"操作不是一个原子操作,需要3步执行 12 //1.在内存中申请空间,2.实例中各参数初始化 3.将实例指向内存地址。 13 //由于指令重排序,会导致2和3的顺序发生变化,其他等待实例初始化的线程获取到不完整的实例。 14 private static volatile SingleTonTest singleTonTest = null; 15 16 public static SingleTonTest getInstance() { 17 if (singleTonTest == null) { 18 synchronized (SingleTonTest.class) { 19 if (singleTonTest == null) { 20 singleTonTest = new SingleTonTest(); 21 } 22 } 23 } 24 return singleTonTest; 25 } 26 }
4.静态内部类:同样是懒加载中的一种初始化方式。类初始化不会引起内部类的初始化。只有调用getInstance()方法时才会执行。该方法是effective java一书中的推荐方式。
优势:代码简洁,线程安全(依据为根据java规范,类的初始化有且仅有一次),按需初始化。
劣势:同饿汉模式,无法通过传递参数实例化对象。
1 /** 2 * @author txba6868 3 * @title: SingleTonTest 4 * @projectName javaAnalysis 5 * @date 2020/1/8 11:08 6 * @desc 单例模式 7 */ 8 public class SingleTonTest { 9 10 11 //静态内部类 12 private static class SingleTonInnerClass { 13 private static SingleTonTest singleTonTest = new 14 SingleTonTest(); 15 } 16 17 public static SingleTonTest getInstance() { 18 return SingleTonInnerClass.singleTonTest; 19 } 20 //构造方法私有化 21 private SingleTonTest(){} 22 23 24 }
另外,以上方式都存在两个同样的问题:可通过reflect创建实例;可通过反序列化生成新的实例。当然,我们可以通过在构造函数中加入一个标识阻止实例的二次创建。反序列化时通过重写方法直接返回当前实例。
但这样做无疑增加了代码的复杂性。
5.枚举类型:枚举方式在jvm层面可防御上述两种攻击方式。目前来看是一种较为安全的单例模式,不过枚举方式在内存方面的占用较静态方法略大。
1 /** 2 * @author txba6868 3 * @title: SingleTonTest 4 * @projectName javaAnalysis 5 * @date 2020/1/8 11:08 6 * @desc 单例模式 7 */ 8 public class SingleTonTest { 9 10 //构造方法私有化 11 private SingleTonTest() { 12 } 13 14 //获取单例对象 15 public static SingleTonTest getInstance() { 16 return SingleTon.INSTANCE.getInstance(); 17 } 18 19 private enum SingleTon { 20 INSTANCE; 21 private SingleTonTest singleTon; 22 23 SingleTon() { 24 singleTon = new SingleTonTest(); 25 } 26 27 private SingleTonTest getInstance() { 28 return singleTon; 29 } 30 } 31 }
参考:https://www.jianshu.com/p/f8267d6ac017(细说java中的几种单例模式)
引申:https://www.cnblogs.com/duanxz/p/3152574.html(单例-被废弃的DCL双重检查加锁)
https://www.jianshu.com/p/d9d9dcf23359(为什么说枚举是最好的单例实现)
// 构造方法私有化(略)