头一次见单例模式讲的如此透彻

2023-06-23 11:50:24 浏览数 (2)

简介

单例模式是一种常用的软件设计模式,用于创建类型。通过单例模式的方法创建的类在当前进程中只有一个实例。单例模式的类只能允许一个实例存在。单例模式的作用是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。

组成部分:

  1. 私有化构造方法。
  2. 私有化内部实例。
  3. 公有静态方法用来获取内部实例。
单例模式单例模式

优缺点

单例模式的优点有:

  • 提供了对唯一实例的受控访问,可以保证对象的唯一性和一致性。
  • 减少了内存开销,避免了频繁的创建和销毁对象。
  • 避免了对资源的多重占用,例如文件操作、数据库连接等。

单例模式的缺点有:

  • 不支持继承和多态,违反了单一职责原则,一个类应该只关心内部逻辑,而不关心外部如何实例化。
  • 不易扩展,如果需要创建多个实例,就需要修改代码,违反了开闭原则,一个类应该对扩展开放,对修改关闭。
  • 不支持有参数的构造函数,如果需要传递参数,就需要修改方法或者定义其他方法。
  • 可能存在反射或者反序列化攻击,破坏单例的唯一性。

应用场景

单例模式适用于以下场景:

  • 需要频繁创建和销毁的对象,例如缓存、线程池、注册表等。
  • 需要控制资源的访问,例如文件操作、数据库连接等。
  • 需要保证对象的唯一性和一致性,例如配置信息、全局变量等。

Java 代码示例

在 Java 中,有五种不同的单例实现方法。其中包括饿汉式、懒汉式、双检锁、静态内部类和枚举类。 单例模式的五种实现原理分别是饿汉式、懒汉式、双重检测、静态内部类和枚举类。它们各自的优缺点如下:

  • 饿汉式:原理是在类加载的时候,就创建并初始化一个静态的实例对象,然后通过一个静态的方法返回这个实例。优点是线程安全,不需要加锁;缺点是不支持延迟加载,可能会浪费资源。
代码语言:javascript复制
public class Singleton {
    private Singleton() {}
    private static Singleton instance;
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 懒汉式:原理是在第一次调用获取实例的方法时,才创建并初始化一个静态的实例对象,然后返回这个实例。为了保证线程安全,需要给获取实例的方法加上synchronized关键字。优点是支持延迟加载,节省资源;缺点是线程不安全,需要加锁,影响性能。
代码语言:javascript复制
public class Singleton {
    private Singleton() {}
    private static final Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}
  • 双重检测:原理是在第一次调用获取实例的方法时,先判断静态的实例对象是否为空,如果为空,则进入同步代码块,再判断一次是否为空,如果为空,则创建并初始化一个静态的实例对象,然后返回这个实例。为了防止指令重排序导致空指针异常,需要给静态的实例对象加上volatile关键字。优点是线程安全,支持延迟加载,不需要加锁;缺点是可能会出现空指针异常,需要使用 volatile 关键字防止指令重排序。
代码语言:javascript复制
public class Singleton {
    private Singleton() {}
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类:原理是利用了 Java 静态内部类的特性,即外部类加载时不会加载内部类,只有在使用到内部类时才会加载。因此,在第一次调用获取实例的方法时,才会加载静态内部类,并创建并初始化一个静态的实例对象,然后返回这个实例。优点是线程安全,支持延迟加载,不需要加锁;缺点是不能防止反射或者反序列化攻击。
代码语言:javascript复制
public class Singleton {
    private Singleton() {}
    private static class Instance {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return Instance.instance;
    }
}
  • 枚举类:原理是利用了Java枚举类型本身的特性,即枚举类型在加载时就会创建所有的枚举常量,并且保证了线程安全性和唯一性。因此,在调用获取实例的方法时,直接返回枚举常量即可。优点是线程安全,简单易用,可以防止反射或者反序列化攻击;缺点是不支持延迟加载,不能继承其他类。
代码语言:javascript复制
public enum Singleton {
     INSTANCE;
}

这些不同的实现方式有不同的适用场景,需要根据具体的需求和条件来选择。在这里,我只能给出一些个人的看法,仅供参考。

  • 如果对内存资源比较敏感,或者单例对象不需要频繁使用,可以考虑使用懒汉式或者双重检测,因为它们支持延迟加载,可以节省资源。
  • 如果对性能比较敏感,或者单例对象需要频繁使用,可以考虑使用饿汉式或者静态内部类,因为它们不需要加锁,可以提高效率。
  • 如果对安全性比较敏感,或者需要防止反射或者反序列化攻击,可以考虑使用枚举类,因为它可以保证实例的唯一性和不可变性。
  • 如果对简洁性比较敏感,或者不需要继承其他类,可以考虑使用枚举类,因为它是最简单的实现方式。

个人来说在编码效率和可维护性上我比较倾向于使用静态内部类的实现方式,既能保证线程安全性,又能支持延迟加载。

Spring 代码示例

在 Spring 框架中,Spring 默认使用单例模式来创建和管理 Bean 对象,但是可以通过 @Scope("singleton") 注解来指定 Bean 对象的作用域。

  • @Scope("singleton"):表示该Bean对象是一个单例对象,在整个Spring容器中只有一个实例。
  • @Scope("prototype"):表示该Bean对象是一个原型对象,在每次请求时都会创建一个新的实例。
  • @Scope("request"):表示该Bean对象的作用域是一个HTTP请求,在同一个请求中只有一个实例。
  • @Scope("session"):表示该Bean对象的作用域是一个HTTP会话,在同一个会话中只有一个实例。

总结

单例模式是一种简单而常用的设计模式,它可以保证一个类只有一个实例,并提供一个全局访问点。单例模式有多种实现方式,各有优缺点。单例模式可以节约系统资源,避免资源冲突,保证对象的唯一性和一致性。但是单例模式也有不利于继承和扩展的缺点,以及可能存在的安全隐患。在使用单例模式时,需要根据具体情况和需求选择合适的方法,并注意避免潜在的问题。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

0 人点赞