目录
- 单例模式的定义及特点。
- 饿汉模式
- 懒汉模式
- 双重锁检查模式
- 枚举模式
- 登记模式
- 总结
单例模式的定义以及特点
定义: 单例模式指一个类只有一个实例,且该实例由类自身创建,并提供全局访问点。
产生背景: 某些对象的创建需要消耗过多的资源,如访问 IO 和数据库等资源。重复创建对象对导致过多的资源消耗。还有一种情形是某些类有且应该只有一个实例。
关键点:
- 构造函数不对外公开,一般为 Private(唯一实例,所以实例由类自身创建);
- 通过一个静态方法或者枚举返回单例类对象(提供全局访问点);
- 确保单例类对象有且只有一个,尤其在多线程环境下(并发情况下线程安全问题);
- 确保单例类对象在反序列化时不会重新构建对象(反序列化时哪怕是私有构造函数也能充血创建实例);
简单实现: 通过将单例类的构造函数私有化,使得客户端代码不能通过 new 的形式手动构造单例类的对象。单例类回暴露一个公有静态方法,提供单例类的唯一对象给外部使用。
注意: - 延迟初始化
- 线程安全;
- 反序列化;
饿汉模式
类一旦加载就会创建自身的实例,保证在调用 getInstance 方法之前单例已经存在。饿汉模式是线程安全的,但是它不满足延迟加载的需求。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInStance() {
return instance;
}
}
懒汉模式
声明一个静态对象,并且在用户第一次调用 getInstance 时进行初始化。懒汉模式满足延迟初始化的需求,但是依旧存在线程安全的问题。
- 普通懒汉模式,满足延迟初始化需求,但是线程不安全。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInStance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 线程安全的懒汉模式,满足延迟初始化的需求,且线程安全。但是每次获取实例都需要同步,造成不必要的同步开销。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInStance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查锁模式(Double Check Lock)
DCL 方式实现单例的有点是既能够在需要时才初始化,又能够保证线程安全,且单例对象初始化后调用 getInstance 方法不需要进行同步。
- instance 必须加上关键字 volatile,禁止重排序,所有的写(write)操作都将发生在读(read)操作之前。这是因为实例化对象可以分解为三步:
- 给 Singlton 的实例分配内存空间;
- 调用 Singlton() 的构造函数,初始化成员字段;
- 将 instance 对象指向刚刚分配的内存空间(此时 instance 就不是 null 了); 但是 Java 编译器允许处理器乱序执行,以及在 JDK 1.5 之前 JMM(Java Memory Model 及 Java 内存模型)中的 Cache、寄存器到内存回写顺序规定,上面 2、3 步的顺序是无法保证的。所以存在先调用 3 步骤再调用 2 步骤,这就可能存在 instance 不为空但是其初始化并未完成的情况。所以需要 volatile 关键字。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public synchronized static Singleton getInStance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类模式
当第一次加载 Singleton 类时并不会初始化 instance,只有在第一次调用 Singleton 当 getInstance() 时才会倒置 instance 被初始化。因为第一次调用 getInstance() 会导致虚拟机加载 SingletonHolder 类。这种方法不仅能保证线程安全,也能保证单例对象的唯一性,同时也延迟了单例类的实例化。
public class Singleton {
private Singleton() {
}
public synchronized static Singleton getInStance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
枚举模式
枚举模式是线程安全的单例模式且只会加载一次。
public enum Singleton {
SINGLETON;
}
登记模式
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void register(String key, Object object) {
objMap.put(key, object);
}
public static Object get(String key) {
return objMap.get(key);
}
}
总结
优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是对一个对象需要频繁创建、销毁时,而创建或销毁时性能又无法优化,单例模式点又是就很明显。
- 单例模式只生成一个实例,所以减少了系统点性能开销,当一个对象的产生需要较多资源时,如读取配置、产生其它依赖对象,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来优化。
- 单例模式可以避免对资源对过多占用,如写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件同时进行操作。
- 单例模式可以在系统设置全局对访问点,优化和共享资源访问,如上文提到对登记模式。 缺点
- 单例模式一般没有接口,扩展困难。
- 单例模式如果持有 Context,那么容易引发内存泄漏,此时传递给单例对 Context 最好是 ApplicationContext。