重学设计模式之单例模式

254 阅读5分钟

目录

  1. 单例模式的定义及特点。
  2. 饿汉模式
  3. 懒汉模式
  4. 双重锁检查模式
  5. 枚举模式
  6. 登记模式
  7. 总结

单例模式的定义以及特点

定义: 单例模式指一个类只有一个实例,且该实例由类自身创建,并提供全局访问点。
产生背景: 某些对象的创建需要消耗过多的资源,如访问 IO 和数据库等资源。重复创建对象对导致过多的资源消耗。还有一种情形是某些类有且应该只有一个实例。
关键点:

  1. 构造函数不对外公开,一般为 Private(唯一实例,所以实例由类自身创建);
  2. 通过一个静态方法或者枚举返回单例类对象(提供全局访问点);
  3. 确保单例类对象有且只有一个,尤其在多线程环境下(并发情况下线程安全问题);
  4. 确保单例类对象在反序列化时不会重新构建对象(反序列化时哪怕是私有构造函数也能充血创建实例); 简单实现: 通过将单例类的构造函数私有化,使得客户端代码不能通过 new 的形式手动构造单例类的对象。单例类回暴露一个公有静态方法,提供单例类的唯一对象给外部使用。
    注意:
  5. 延迟初始化
  6. 线程安全;
  7. 反序列化;

饿汉模式

类一旦加载就会创建自身的实例,保证在调用 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)操作之前。这是因为实例化对象可以分解为三步:
  1. 给 Singlton 的实例分配内存空间;
  2. 调用 Singlton() 的构造函数,初始化成员字段;
  3. 将 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);
    }
}

总结

优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是对一个对象需要频繁创建、销毁时,而创建或销毁时性能又无法优化,单例模式点又是就很明显。
  2. 单例模式只生成一个实例,所以减少了系统点性能开销,当一个对象的产生需要较多资源时,如读取配置、产生其它依赖对象,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来优化。
  3. 单例模式可以避免对资源对过多占用,如写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件同时进行操作。
  4. 单例模式可以在系统设置全局对访问点,优化和共享资源访问,如上文提到对登记模式。 缺点
  5. 单例模式一般没有接口,扩展困难。
  6. 单例模式如果持有 Context,那么容易引发内存泄漏,此时传递给单例对 Context 最好是 ApplicationContext。