5分钟掌握设计模式之:单例模式
本文思维导图
什么是单例模式
简介
-
单例模式属于创建型模式,它保证一个类只有一个实例,并提供唯一节点访问该实例。
-
单例模式主要解决一个全局使用的类被频繁地创建与销毁对象,浪费系统资源的问题。
-
使用单例模式时首先要判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
-
单例模式的构造函数是私有的。
单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用。
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
- 保证一个类只有一个实例。
如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,在单例模式下你会获得之前已创建的对象,而不是普通构造函数总是返回一个新对象。
- 为该实例提供一个全局访问节点。
优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
缺点
-
没有接口
-
不能继承
-
违反了 单一职责原则*
单一职责原则:修改一个类的原因只能有一个。
为什么要使用单例模式?
单例模式的应用场景
- 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。
- 如果你需要更加严格地控制全局变量, 可以使用单例模式。
单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。
请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。
如何使用单例模式?
单例模式的结构
-
单例(Singleton)类声明了一个名为getInstance获取实例的静态方法来返回其所属类的一个相同实例。
-
单例的构造函数必须对客户端(Client)代码隐藏。调用获取实例方法必须是获取单例对象的唯一方式。
单例模式的实现特点
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
- 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。
单例模式的几种实现方式
懒汉式,线程不安全
- 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式,线程安全
- 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized 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;
}
}
双检锁/双重校验锁(DCL,double-checked locking)
- DCL采用双锁机制,并在getSingleton方法中对singleton进行了两次判空。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
登记式/静态内部类
- 这种方式能达到DCL一样的功效,但实现更简单。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
- 更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
总结
- 单例模式的作用是确保某个类只有一个实例,避免产生多个对象消耗过多的资源,或者某个类型的对象只应该有一个。
实现单例模式主要关键点:
- 构造函数不对外开放,一般为Private。
- 通过一个静态方法或者枚举返回单例类对象。
- 确保单例类的对象有且只有一个,尤其是在多线程环境下。
- 确保单例类对象在反序列化时不会重新构建对象。
单例模式是运用频率很高的模式。在客户端通常没有高并发的情况,选择哪种实现方式都不会有太大的影响。
但一般情况下,使用饿汉方式是不错的选择,只有在要明确实现 lazy loading 效果时,才会使用登记方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。