单例模式
单例模式
2019-05-30 2041 ℃
定义
单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式的要点有三个:
- 一是某个类只能有一个实例;
- 二是它必须自行创建这个实例;
- 三是它必须自行向整个系统提供这个实例。
结构
- 单例(Singleton): 构造方法
优点
- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
适用场景
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式
代码实现
懒汉式(线程不安全)
public class UnsafeLazySingleton {
private static UnsafeLazySingleton INSTANCE;
private UnsafeLazySingleton() {}
public static UnsafeLazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new UnsafeLazySingleton();
}
return INSTANCE;
}
}
是否 Lazy 初始化: 是
是否多线程安全: 否
实现难度: 易
描述: 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
优点: 达到了Lazy Loading的效果
缺点: 不支持多线程
推荐指数: ★☆
饿汉式(线程安全)
public class SecureLazySingleton {
private static SecureLazySingleton INSTANCE;
private SecureLazySingleton() {}
public static synchronized SecureLazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new SecureLazySingleton();
}
return INSTANCE;
}
}
是否 Lazy 初始化: 是
是否多线程安全: 是
实现难度: 易
描述: 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点: 达到了Lazy Loading的效果,支持多线程
缺点: 效率低
推荐指数: ★★
饿汉式
public class EagerSingleton {
private final static EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return INSTANCE;
}
}
是否 Lazy 初始化: 否
是否多线程安全: 是
实现难度: 易
描述: 这种方式比较常用,但容易产生垃圾对象。
优点: 没有加锁,执行效率提高,避免了线程同步问题。
缺点: 在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
推荐指数: ★★★★☆
双检锁/双重校验锁(DCL,即 double-checked locking)
public class DclSingleton {
private static volatile DclSingleton INSTANCE;
private DclSingleton(){}
public static DclSingleton getInstance(){
if (INSTANCE == null) {
synchronized (DclSingleton.class) {
if (INSTANCE == null) {
INSTANCE = new DclSingleton();
}
}
}
return INSTANCE;
}
}
是否 Lazy 初始化: 是
是否多线程安全: 是
实现难度: 较复杂
描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
优点: 线程安全;延迟加载;效率较高
缺点: 实现较复杂,需JDK5以上版本
推荐指数: ★★★★
登记式/静态内部类
public class StaticInnerSingleton {
private StaticInnerSingleton(){}
private static class SingletonInstance {
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
是否 Lazy 初始化: 是
是否多线程安全: 是
实现难度: 一般
描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
优点: 线程安全,延迟加载,效率高。
缺点: 只适用于静态域
推荐指数: ★★★☆
枚举
public enum EnumSingleton {
INSTANCE;
public void whateverMethod() {
}
}
是否 Lazy 初始化: 否
是否多线程安全: 是
实现难度: 易
描述: 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
优点: 绝对安全,实现简单,效率高。
缺点: 需JDK5以上版本
推荐指数: ★★★★★
建议
不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。
文章链接:http://www.iszhouhua.com/singleton-pattern.html
发表时间:2019-05-30 22:44
最后更新时间:2019-05-30 22:51
0条评论