单例模式 (Singleton Pattern)

定义:

单例模式(Singleton Pattern)是一种常用的软件设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式主要用于控制对某个资源或服务的访问,确保整个系统中只有一个对象实例负责这些操作。

单例模式的关键特点包括:

  1. 单一实例
    • 该模式确保一个类只有一个实例存在。通常通过隐藏该类的构造函数,并提供一个静态方法来创建和获取这个唯一实例。
  2. 全局访问点
    • 单例类提供了一个全局访问点,通过这个访问点可以访问唯一实例。这通常是一个静态方法,例如 getInstance()
  3. 自我实例化
    • 单例类负责创建自己的唯一实例,并确保没有其他实例被创建。
  4. 延迟初始化(可选):
    • 在单例模式中,实例通常在首次使用时创建,这称为延迟初始化。这有助于减少资源的浪费和提高性能。
解决的问题:
  • 控制实例创建
    • 确保一个类只有一个实例被创建。这对于控制资源的使用非常重要,尤其是当对象的创建和管理需要消耗大量资源时(如访问共享资源或共享服务)。
  • 全局访问点
    • 提供一个全局访问点以访问该类的唯一实例。这意味着从系统的任何地方都可以访问这个实例,而不需要重新创建或通过特定对象传递。
  • 替代全局变量
    • 单例模式提供了一种更好的方式来创建和管理全局对象,避免了使用全局变量所带来的潜在风险和复杂性。
  • 共享资源或状态管理
    • 当多个对象需要共享同一资源或状态时(例如配置信息、缓存、数据库连接池等),单例模式可以确保所有对象都使用相同的实例,从而实现有效的资源共享和管理。
  • 控制并发访问
    • 在多线程环境中,单例模式可以被用来控制对共享资源的并发访问,确保在任何时刻只有一个实例管理着资源,减少资源冲突的可能性。
使用场景:
  • 资源共享
    • 当系统中的不同组件需要共享相同的资源或服务时,如数据库连接池、日志记录、配置管理等。单例模式确保这些组件都访问同一个实例,从而实现资源的有效共享。
  • 全局状态或配置
    • 当需要在应用程序的不同部分之间共享全局状态或配置信息时。例如,应用程序的配置设置通常在多个地方被使用,单例模式可以提供一个统一的接口来访问这些设置。
  • 控制资源访问
    • 对于需要严格控制客户端如何以及何时访问某个资源的场景。单例模式可以确保对资源的访问在整个应用程序中是同步的,从而避免资源冲突和数据不一致。
  • 日志记录
    • 日志记录通常在整个应用程序中是统一的操作。使用单例模式可以确保全局只有一个日志记录器在运行,从而保持日志记录的一致性。
  • 硬件接口访问
    • 当访问硬件资源时(如打印机或文件系统),通常需要确保全局只有一个实例负责与硬件的交互,避免可能的资源冲突。
  • 性能优化
    • 在创建对象特别消耗资源的情况下,单例模式可以减少内存开销和性能开销,因为它避免了重复创建和销毁同一对象。
示例代码:
1. 懒汉式(线程不安全)
public class SingletonLazy {
    private static SingletonLazy instance;

    private SingletonLazy() {}

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}
  • 优点:延迟加载,只有在需要时才创建实例。
  • 缺点:线程不安全,如果多个线程同时访问,可能会创建多个实例。
2. 懒汉式(线程安全)
public class SingletonLazySynchronized {
    private static SingletonLazySynchronized instance;

    private SingletonLazySynchronized() {}

    public static synchronized SingletonLazySynchronized getInstance() {
        if (instance == null) {
            instance = new SingletonLazySynchronized();
        }
        return instance;
    }
}
  • 优点:延迟加载,线程安全。
  • 缺点:同步方法降低了效率。
3. 饿汉式(线程安全)
public class SingletonEager {
    private static final SingletonEager INSTANCE = new SingletonEager();

    private SingletonEager() {}

    public static SingletonEager getInstance() {
        return INSTANCE;
    }
}
  • 优点:简单,线程安全。
  • 缺点:类加载时就完成实例化,没有达到延迟加载的效果。
4. 双重校验锁(线程安全)
public class SingletonDoubleCheckedLocking {
    private static volatile SingletonDoubleCheckedLocking instance;

    private SingletonDoubleCheckedLocking() {}

    public static SingletonDoubleCheckedLocking getInstance() {
        if (instance == null) {
            synchronized (SingletonDoubleCheckedLocking.class) {
                if (instance == null) {
                    instance = new SingletonDoubleCheckedLocking();
                }
            }
        }
        return instance;
    }
}
  • 优点:既保证了延迟加载,又保证了线程安全,效率较高。
  • 缺点:实现复杂。
5. 静态内部类(线程安全)
public class SingletonStaticInnerClass {
    private static class SingletonHolder {
        private static final SingletonStaticInnerClass INSTANCE = new SingletonStaticInnerClass();
    }

    private SingletonStaticInnerClass() {}

    public static SingletonStaticInnerClass getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  • 优点:实现简单,延迟加载,线程安全。
  • 缺点:实现方式可能不够直观。
6. 枚举方式(线程安全)
public enum SingletonEnum {
    INSTANCE;

    public void doSomething() {
        // 执行操作
    }
}
  • 优点:简单,提供了序列化机制,绝对防止多次实例化。
  • 缺点:不是懒加载模式(非延迟加载)。

每种方法都有其适用场景,根据实际需求选择最合适的实现方式。

主要符合的设计原则:
  1. 单一职责原则(Single Responsibility Principle)
    • 单例模式确保了类有一个单一的职责,即管理自己的唯一实例。这个类负责创建、管理这个实例,并提供一个全局访问点。
  2. 开闭原则(Open-Closed Principle)
    • 单例模式在某种程度上符合开闭原则。一旦单例类被正确实现,它就可以在不修改源代码的情况下保持开放性(例如,可以继承自单例类)。但是,由于单例类控制了实例化过程,扩展其功能可能需要修改源代码,这在某种程度上限制了其对开闭原则的遵循。
  3. 里氏替换原则(Liskov Substitution Principle)
    • 单例模式不完全符合里氏替换原则。由于单例类的构造函数是私有的,子类无法直接继承这个构造函数。如果需要继承单例类,就需要在子类中重写实例控制的逻辑。
  4. 接口隔离原则(Interface Segregation Principle)
    • 单例模式并不直接关注接口隔离原则,因为它通常不涉及接口的多重继承。单例类提供的接口是围绕其唯一实例的管理。
  5. 依赖倒转原则(Dependency Inversion Principle)
    • 单例模式可以支持依赖倒转原则。虽然单例对象通常由单例类自身管理,但是单例类可以实现接口,使得高层模块依赖于抽象而不是具体的实例。

综上所述,单例模式主要体现了单一职责原则,但在其他几个设计原则方面的适用性较为有限。它专注于控制类实例的数量,而在继承性和接口设计方面的灵活性不如其他设计模式。

在JDK中的应用:
  • java.lang.Runtime:
    • Runtime 类提供了与Java运行时环境交互的方法。它是一个单例,因为每个Java应用程序有一个且只有一个运行时环境,所以 Runtime.getRuntime() 方法提供了获取这个唯一实例的全局访问点。
  • java.awt.Desktopjava.awt.Toolkit:
    • 在Java AWT库中,Desktop 类和 Toolkit 类被用来提供平台相关的功能,如桌面或工具包服务。这些类通常以单例模式实现,提供一个全局访问点。
  • Java日志API
    • 在Java日志API中,java.util.logging.LogManager 类被用来维护日志服务的全局状态。它通常被实现为单例,因为全局只需要一个日志管理器。
  • Spring框架中的Bean
    • 虽然不是JDK的一部分,但值得一提的是,在Spring框架中,Bean默认是单例的。Spring容器为每个Bean定义创建一个且只有一个实例。

这些例子展示了单例模式在Java标准库中的应用,特别是在需要全局访问点和统一管理资源的情况下。单例模式通过确保类的单个实例全局可用,简化了对共享资源或服务的访问和管理。

在Spring中的应用:
  • Spring Bean的默认作用域
    • 在Spring框架中,默认情况下,所有通过Spring容器创建的Bean都是单例的。这意味着每个由Spring容器管理的Bean类的实例在Spring的整个应用上下文中只有一个。这样做减少了对象的创建成本,同时也减少了内存的消耗。
  • 服务和组件
    • Spring中的服务和组件(如数据源、事务管理器、工厂类等)通常被配置为单例,因为这些对象通常维护着自己的状态(如数据库连接池),在整个应用中共享这个状态更加高效。
  • 全局状态的维护
    • Spring使用单例模式来维护应用级别的全局状态,如配置属性和环境变量。
  • Spring Security的单例Bean
    • 在Spring Security中,像认证管理器(AuthenticationManager)、用户服务(UserDetailsService)等关键组件通常作为单例存在。

单例模式在Spring框架中的应用帮助管理了Bean的生命周期和状态,确保了资源的高效利用和应用组件的统一访问。同时,由于Spring容器管理着Bean的创建和销毁,所以它还减轻了开发者处理单例实例化和线程安全问题的负担。然而,使用单例Bean时,需要注意Bean的状态管理,避免因状态共享导致的潜在问题。


你可能感兴趣的:(设计模式,单例模式,java,设计模式)