定义:
单例模式(Singleton Pattern)是一种常用的软件设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式主要用于控制对某个资源或服务的访问,确保整个系统中只有一个对象实例负责这些操作。
单例模式的关键特点包括:
- 单一实例 :
- 该模式确保一个类只有一个实例存在。通常通过隐藏该类的构造函数,并提供一个静态方法来创建和获取这个唯一实例。
- 全局访问点 :
- 单例类提供了一个全局访问点,通过这个访问点可以访问唯一实例。这通常是一个静态方法,例如
getInstance()
。
- 单例类提供了一个全局访问点,通过这个访问点可以访问唯一实例。这通常是一个静态方法,例如
- 自我实例化 :
- 单例类负责创建自己的唯一实例,并确保没有其他实例被创建。
- 延迟初始化 (可选):
- 在单例模式中,实例通常在首次使用时创建,这称为延迟初始化。这有助于减少资源的浪费和提高性能。
解决的问题:
- 控制实例创建 :
- 确保一个类只有一个实例被创建。这对于控制资源的使用非常重要,尤其是当对象的创建和管理需要消耗大量资源时(如访问共享资源或共享服务)。
- 全局访问点 :
- 提供一个全局访问点以访问该类的唯一实例。这意味着从系统的任何地方都可以访问这个实例,而不需要重新创建或通过特定对象传递。
- 替代全局变量 :
- 单例模式提供了一种更好的方式来创建和管理全局对象,避免了使用全局变量所带来的潜在风险和复杂性。
- 共享资源或状态管理 :
- 当多个对象需要共享同一资源或状态时(例如配置信息、缓存、数据库连接池等),单例模式可以确保所有对象都使用相同的实例,从而实现有效的资源共享和管理。
- 控制并发访问 :
- 在多线程环境中,单例模式可以被用来控制对共享资源的并发访问,确保在任何时刻只有一个实例管理着资源,减少资源冲突的可能性。
使用场景:
- 资源共享 :
- 当系统中的不同组件需要共享相同的资源或服务时,如数据库连接池、日志记录、配置管理等。单例模式确保这些组件都访问同一个实例,从而实现资源的有效共享。
- 全局状态或配置 :
- 当需要在应用程序的不同部分之间共享全局状态或配置信息时。例如,应用程序的配置设置通常在多个地方被使用,单例模式可以提供一个统一的接口来访问这些设置。
- 控制资源访问 :
- 对于需要严格控制客户端如何以及何时访问某个资源的场景。单例模式可以确保对资源的访问在整个应用程序中是同步的,从而避免资源冲突和数据不一致。
- 日志记录 :
- 日志记录通常在整个应用程序中是统一的操作。使用单例模式可以确保全局只有一个日志记录器在运行,从而保持日志记录的一致性。
- 硬件接口访问 :
- 当访问硬件资源时(如打印机或文件系统),通常需要确保全局只有一个实例负责与硬件的交互,避免可能的资源冲突。
- 性能优化 :
- 在创建对象特别消耗资源的情况下,单例模式可以减少内存开销和性能开销,因为它避免了重复创建和销毁同一对象。
示例代码:
1. 懒汉式(线程不安全)
java
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
- 优点:延迟加载,只有在需要时才创建实例。
- 缺点:线程不安全,如果多个线程同时访问,可能会创建多个实例。
2. 懒汉式(线程安全)
java
public class SingletonLazySynchronized {
private static SingletonLazySynchronized instance;
private SingletonLazySynchronized() {}
public static synchronized SingletonLazySynchronized getInstance() {
if (instance == null) {
instance = new SingletonLazySynchronized();
}
return instance;
}
}
- 优点:延迟加载,线程安全。
- 缺点:同步方法降低了效率。
3. 饿汉式(线程安全)
java
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() {
return INSTANCE;
}
}
- 优点:简单,线程安全。
- 缺点:类加载时就完成实例化,没有达到延迟加载的效果。
4. 双重校验锁(线程安全)
java
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. 静态内部类(线程安全)
java
public class SingletonStaticInnerClass {
private static class SingletonHolder {
private static final SingletonStaticInnerClass INSTANCE = new SingletonStaticInnerClass();
}
private SingletonStaticInnerClass() {}
public static SingletonStaticInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 优点:实现简单,延迟加载,线程安全。
- 缺点:实现方式可能不够直观。
6. 枚举方式(线程安全)
java
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
// 执行操作
}
}
- 优点:简单,提供了序列化机制,绝对防止多次实例化。
- 缺点:不是懒加载模式(非延迟加载)。
每种方法都有其适用场景,根据实际需求选择最合适的实现方式。
主要符合的设计原则:
- 单一职责原则(Single Responsibility Principle) :
- 单例模式确保了类有一个单一的职责,即管理自己的唯一实例。这个类负责创建、管理这个实例,并提供一个全局访问点。
- 开闭原则(Open-Closed Principle) :
- 单例模式在某种程度上符合开闭原则。一旦单例类被正确实现,它就可以在不修改源代码的情况下保持开放性(例如,可以继承自单例类)。但是,由于单例类控制了实例化过程,扩展其功能可能需要修改源代码,这在某种程度上限制了其对开闭原则的遵循。
- 里氏替换原则(Liskov Substitution Principle) :
- 单例模式不完全符合里氏替换原则。由于单例类的构造函数是私有的,子类无法直接继承这个构造函数。如果需要继承单例类,就需要在子类中重写实例控制的逻辑。
- 接口隔离原则(Interface Segregation Principle) :
- 单例模式并不直接关注接口隔离原则,因为它通常不涉及接口的多重继承。单例类提供的接口是围绕其唯一实例的管理。
- 依赖倒转原则(Dependency Inversion Principle) :
- 单例模式可以支持依赖倒转原则。虽然单例对象通常由单例类自身管理,但是单例类可以实现接口,使得高层模块依赖于抽象而不是具体的实例。
综上所述,单例模式主要体现了单一职责原则,但在其他几个设计原则方面的适用性较为有限。它专注于控制类实例的数量,而在继承性和接口设计方面的灵活性不如其他设计模式。
在JDK中的应用:
java.lang.Runtime
:Runtime
类提供了与Java运行时环境交互的方法。它是一个单例,因为每个Java应用程序有一个且只有一个运行时环境,所以Runtime.getRuntime()
方法提供了获取这个唯一实例的全局访问点。
java.awt.Desktop
或java.awt.Toolkit
:- 在Java AWT库中,
Desktop
类和Toolkit
类被用来提供平台相关的功能,如桌面或工具包服务。这些类通常以单例模式实现,提供一个全局访问点。
- 在Java AWT库中,
- Java日志API :
- 在Java日志API中,
java.util.logging.LogManager
类被用来维护日志服务的全局状态。它通常被实现为单例,因为全局只需要一个日志管理器。
- 在Java日志API中,
- 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的状态管理,避免因状态共享导致的潜在问题。