常见的几种单例模式
单例模式是常用设计模式,核心结构里只包含一个被称为单例的特殊类:一个类只有一个实例,也就是只有一个对象实例。
系统里有些类必须唯一。例如可以有多个打印任务,但同时只能有一个在工作;售票时一共 100 张票、多个窗口同时卖,余量必须是单例,否则会超售。窗口对象如果不唯一化,会弹出多个窗口;内容相同却重复创建,就是浪费资源。
一、应用场景
来源:《大话设计模式》。需求:在前端创建工具箱窗口,要么不出现,出现也只出现一个。
遇到的问题:每次点击菜单都会重复创建「工具箱」窗口。解决方案一:用 if,每次创建前先判断是否为 null,为 null 再 new。如果 5 个地方都要实例出工具箱,这个小 bug 要改 5 处,代码重复、利用率低。
解决方案二:用单例模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。
下面几种写法会发现:所有单例都用静态方法创建,所以单例对象存在内存的静态共享区。(可参考:https://zhidao.baidu.com/question/2206072272164938188.html)
二、懒汉式与饿汉式
-
懒汉式:类加载时不初始化。 -
饿汉式:类加载时就完成初始化,类加载比较慢,但获取对象的速度快。
| 写法 | 类型 | 要点 |
|---|---|---|
| 第一种 | 懒汉,线程不安全 | lazy loading 明显,多线程不能正常工作 |
| 第二种 | 懒汉,线程安全 | getInstance 加 synchronized,效率低 |
| 第三种 | 饿汉 | 类装载时实例化,无 lazy loading |
| 第四种 | 饿汉变种 | 静态块初始化,效果同第三种 |
| 第五种 | 静态内部类 | 调用 getInstance 才装载内部类 |
| 第六种 | 枚举 | Effective Java 提倡,防反序列化 |
| 第七种 | 双重校验锁 | JDK 1.5 之后才可靠 |
三、七种写法
第一种(懒汉,线程不安全)
这种写法 lazy loading 很明显,但是致命的是在多线程不能正常工作。
第二种(懒汉,线程安全)
在 getInstance() 里加了 synchronized。多线程能工作,也有 lazy loading,但因为锁效率很低,大多数情况下并不需要同步。
第三种(饿汉)
基于 classloader 机制避免了多线程同步问题。不过 instance 在类装载时就实例化,没有达到 lazy loading 的效果。
第四种(饿汉,变种)
表面差别挺大,其实和第三种差不多,都是类初始化即实例化 instance。
第五种(静态内部类)
同样利用 classloader 保证初始化 instance 时只有一个线程。和第三、四种的细微差别:那两种只要 Singleton 类被装载,instance 就会被实例化(没有 lazy loading);这种方式 Singleton 被装载了,instance 不一定初始化。因为 SingletonHolder 没有被主动使用,只有显式调用 getInstance 才会装载内部类,从而实例化。如果实例化很耗资源、想延迟加载,又不希望 Singleton 在别处被用到时就提前 new,这种方式比第三、四种更合理。
第六种(枚举)
这是 Effective Java 作者 Josh Bloch 提倡的方式:能避免多线程同步问题,还能防止反序列化重新创建对象。不过 enum 是 1.5 才加入的特性,实际工作中很少看见有人这么写。
第七种(双重校验锁)
这是第二种的升级版,俗称双重检查锁定。详细介绍:http://www.ibm.com/developerworks/cn/java/j-dcl.html。在 JDK 1.5 之后,双重检查锁定才能够正常达到单例效果。
转至:https://www.cnblogs.com/Ycheng/p/7169381.html
一句话总结:单例保证一个类只有一个实例;懒汉延迟创建、饿汉类加载即创建,常用静态内部类或双重校验锁,枚举还能防反序列化。
转载请注明来源:常见的几种单例模式







