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