设计模式之单例模式

mac2022-06-30  77

1. 概念:

某个类有且仅有一个实例,通过自行实例化向整个系统提供这个实例,这种设计模式被称作单例模式。

2. 特点:

1. 单例类只能有一个实例 2. 单例类必须自己创建自己的唯一实例 3. 单例类必须给所有其他对象提供这一实例

3. 关键点:

1. 构造函数私有化 2. 通过一个静态方法或枚举返回单例对象 3. 确保单例类的对象有且仅有一个,特别是多线程环境下 4. 确保单例类在反序列化时不会重新构建对象

4. 实现方式:

4.1 饿汉式

public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } 在单例类初始化时,已经自行实例化,所以饿汉式单例是线程安全的,每次获取单例对象时直接返回该实例,这样节省时间,但由于实例本身是静态的,会一直占据内存空间。

4.2 懒汉式

public class Singleton { private static Singleton instance; private Singleton() { } public synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 只有在使用时才会进行实例化,此种方式实现是线程不安全的,虽然在一定程度上节省了内存空间,但同时导致时间的损耗,而且每次调用getInstance时都进行同步,造成不必要的同步开销。

4.3 Double Check Lock (DLC)

public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } DLC方式实现单例既能够在需要时才会实例化,又能保证线程安全,在实例化之后调用getInstance不再进行同步锁。 另外,上述代码中instance = new Singleton()语句,实际并不是一个原子操作,它可以分为三个步骤: 1. 给Singleton的实例分配内存; 2. 调用Singleton的构造函数,初始化成员变量; 3. 将instance对象指向分配的内存空间(此时instance != null)。 但是由于Java编译器允许处理器乱序执行,在JDK1.5之前的JVM中,上述2和3的执行顺序是无法保证的,也就是说执行顺序有可能是1-2-3或者1-3-2。如果是后者的话,在多线程并发的情况下就有可能出错。在JDK1.5之后可以使用volatile关键字,保证instance对象每次都是从主内存中读取。

4.4 静态内部类单例模式

public class Singleton { private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } private static class SingletonHolder { private static final Singleton instance = new Singleton(); } } 此方式的单例在第一次加载Singleton类时并不会实例化,只有第一次调用getInstance方法时才会实例化。第一次调用getInstance会导致虚拟机加载SingletonHolder类,这种方式既能确保线程安全,也能保证对象唯一,同时也延迟了单例的实例化,这是推荐使用的单例模式实现方式。

4.5 枚举单例

public enum Singleton { INSTANCE; } 默认枚举实例的创建都是线程安全的,并且在任何情况下都是只有一个实例。 其他方式的单例,在反序列化后readObject方法会返回一个新实例,枚举单例不存在此问题,其他方式的单例可以通过重写readResolve方法避免: private Object readResolve() throws ObjectStreamException { return instance; }

4.6 使用容器实现单例

public class SingletonManager { private static HashMap<String, Object> map = new HashMap(); private SingletonManager() { } public static void registerService(String key, Object instance) { if (!map.containsKey(key) { map.put(key, instance); } } public static Object getService(String key) { return map.get(key); } } 在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象。这种方式可以管理多种类型的单例,并且可以在使用时可以通过统一的借口进行获取,降低了使用成本,也隐藏了具体实现,降低耦合度。 例如Android系统中,使用context获取系统级别的服务,就是用的该方式

注意:

单例对象如果持有Context,很容易引发内存泄漏,最好使用Application Context。
参考资料:

《Android源码设计模式解析与实战》

转载于:https://www.cnblogs.com/RMBP975/p/6607978.html

最新回复(0)