设计模式 创建型 单例模式(Singleton Pattern)与 常见技术框架应用 解析
单例模式是一种创建型设计模式,旨在确保某个类在应用程序的生命周期内只有一个实例,并提供一个全局访问点来获取该实例。这种设计模式在需要控制资源访问、避免频繁创建和销毁对象的场景中尤为有用。
一、核心思想
单例模式的核心思想是限制类的实例化次数,确保整个应用程序中只有一个实例存在,并且这个实例可以被全局访问。这通常通过私有化构造函数、提供一个静态方法来获取实例的方式实现。
- 唯一性:保证一个类在系统中只有一个实例。
- 控制访问:提供一个全局访问点来获取该实例。
- 延迟初始化(可选):实例化可以延迟到第一次使用时,以节省资源。
实现单例模式的关键要素:
- 私有构造函数:防止外部通过
new
关键字创建新的实例。 - 静态方法或属性:用于存储和返回唯一的实例。
- 线程安全:如果应用是多线程的,则需要确保在并发环境下也能正确工作。
二、定义与结构
定义:单例模式确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
结构:
- 单例类:负责创建和管理唯一的实例。
- 静态成员变量:用于保存唯一的实例引用。
- 获取实例方法:通常是一个静态方法,用于返回唯一的实例。
- 私有构造函数:防止外部通过构造函数创建多个实例。
角色
- 单例类:包含私有构造函数、静态成员变量和获取实例的静态方法。
三、实现步骤及代码示例
以Java为例,单例模式的实现方式有多种,包括饿汉式、懒汉式、双重检查锁定(Double-Checked Locking)和静态内部类实现方式等。以下是几种常见实现的代码示例:
1、饿汉式
- 特点:在类加载时就创建实例,没有延迟加载的效果,但避免了多线程的同步问题。
- 优点:线程安全,执行效率高。
- 缺点:可能导致内存浪费,因为实例在类加载时就已创建,即使未使用。
public class HungrySingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final HungrySingleton hungry = new HungrySingleton();
private HungrySingleton() {
// 防止反序列化时重新创建实例
if (hungry != null) {
throw new RuntimeException("请使用 HungrySingleton.getInstance() 方法获取一个单例实例");
}
}
public static HungrySingleton getInstance() {
return hungry;
}
// 其他方法...
}
2、懒汉式(线程不安全)
-
特点:按需加载,节省资源,只有在确实需要的时候才会创建实例。但存在线程安全问题。
-
优点:延迟加载,提高了程序启动的速度。
-
缺点:在多线程环境下需要额外的同步机制来保证线程安全。
-
线程不安全的懒汉式示例代码:
public class LazySingleton {
private static LazySingleton lazyMan;
public LazySingleton() {
// 构造函数可以为空或包含初始化代码
}
public static LazySingleton getInstance() {
if (lazyMan == null) {
lazyMan = new LazySingleton();
}
return lazyMan;
}
// 其他方法...
}
上述代码在多线程环境下可能会出现多个实例,因此线程不安全。
注意:懒汉式(线程不安全)在多线程环境下可能会导致多个实例被创建,因此通常不推荐使用。
3、双重检查锁定(Double-Checked Locking)
线程安全的懒汉式示例代码(双重检查锁定):
public class LazySafe {
private static volatile LazySafe instance = null;
private LazySafe() {}
public static LazySafe getInstance() {
if (instance == null) {
synchronized (LazySafe.class) {
if (instance == null) {
instance = new LazySafe();
}
}
}
return instance;
}
}
使用volatile关键字确保在多线程环境中正确处理,双重检查锁定保证了线程安全和性能。
4、静态内部类实现方式
-
特点:利用Java的类加载机制实现延迟加载,线程安全且高效。
-
优点:实现简单,无需额外的同步机制。
-
缺点:无法支持非静态单例需求。
-
示例代码:
public class InnerClassSingleton {
private InnerClassSingleton() {
// 构造函数可以为空或包含初始化代码
}
private static class Holder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return Holder.INSTANCE;
}
// 其他方法...
}
5、枚举单例
-
特点:实现简单,线程安全,防止反射和序列化破坏。
-
优点:最简洁的实现方式,由Java语言本身保证线程安全性。
-
缺点:不支持延迟加载。
-
示例代码:
public enum Singleton {
INSTANCE;
public void someMethod() {
// 单例方法逻辑
}
}
四、JavaScript中的单例模式实现
基本实现
class Singleton {
constructor() {
if (typeof Singleton.instance === 'object') {
return Singleton.instance;
}
Singleton.instance = this;
}
static getInstance() {
if (!this.instance) {
new this();
}
return this.instance;
}
someBusinessLogic() {
// ...业务逻辑...
}
}
// 使用示例
const instanceA = Singleton.getInstance();
const instanceB = Singleton.getInstance();
console.log(instanceA === instanceB); // true
模块模式下的单例
在JavaScript中,模块本身就是一个天然的单例,因为每个模块只会被加载一次。因此,可以利用ES6模块来实现单例模式。
// singleton.js
const singleton = (() => {
let privateState = {}; // 私有状态
function somePrivateMethod() {
// 私有方法
}
return {
publicMethod: function() {
// 公共方法
somePrivateMethod();
},
getPrivateState: function() {
return privateState;
}
};
})();
export default singleton;
// 在其他文件中导入并使用
import singleton from './singleton';
singleton.publicMethod();
console.log(singleton.getPrivateState());
线程安全的单例(适用于Node.js)
当涉及到多线程环境时,如Node.js worker_threads,可能需要确保线程安全。
class ThreadSafeSingleton {
constructor() {
if (!ThreadSafeSingleton.instance) {
ThreadSafeSingleton.instance = this;
}
return ThreadSafeSingleton.instance;
}
static getInstance() {
if (!this.instance) {
// 如果是在多线程环境中,这里应该加入锁机制
// 例如使用Promise或其他同步机制来保证线程安全
new this();
}
return this.instance;
}
someBusinessLogic() {
// ...业务逻辑...
}
}
使用立即执行函数表达式(IIFE)
这是一种经典的JavaScript单例实现方式,尤其是在不支持模块化的旧版本浏览器中。
const singleton = (function () {
const privateState = {}; // 私有状态
function privateMethod() {
// 私有方法
}
return {
publicMethod: function () {
// 公共方法
privateMethod();
},
getPrivateState: function () {
return privateState;
}
};
})();
五、常见技术框架应用代码示例
在Spring框架中,单例模式也得到了广泛应用。Spring容器默认创建的Bean是单例的,即在整个Spring IoC容器中,一个Bean只会有一个实例。以下是Spring中配置单例Bean的示例:
<bean id="myBean" class="com.example.MyBean" singleton="true"/>
或者,在基于注解的配置中,可以通过@Component
或@Bean
注解来定义Bean,并默认其为单例:
@Component
public class MyBean {
// ...
}
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
六、应用场景
单例模式常用于以下场景:
- 日志记录器:在整个应用程序中,通常只需要一个日志记录器来记录日志信息。
- 配置管理器:应用程序的配置信息通常只需要一个实例来管理,以确保配置的一致性。
- 数据库连接池:为了有效地管理数据库连接,避免频繁地创建和销毁连接,通常使用单例模式来创建数据库连接池。
- 线程池:管理和复用线程,避免频繁地创建和销毁线程,提高系统性能。
- 全局计数器:在需要全局唯一的计数器时,可以使用单例模式。
- 购物车服务:在电子商务网站中,用户的购物车应当是唯一的。
- 窗口管理器:图形界面程序中,窗口管理器应确保只有一个实例来协调所有窗口的行为。
七、优缺点
优点:
- 延迟加载:只有在需要时才创建实例,节省资源。
- 全局访问点:提供一个全局访问对象的方式,方便在不同模块和组件之间共享资源。
- 控制资源:在需要限制实例数量的场景下(如数据库连接池、日志系统),单例模式能够确保系统中只有一个实例在操作资源。
缺点:
- 线程安全问题:在多线程环境下,需要额外的同步机制来确保线程安全,可能会影响性能。
- 单例类的职责单一:单例模式通常要求单例类只承担一个职责,否则可能会违背单一职责原则,导致代码难以维护。
综上所述,单例模式是一种非常有用的设计模式,在需要控制资源访问和避免频繁创建对象的场景中发挥着重要作用。然而,在使用时也需要注意其潜在的缺点,并根据具体场景选择合适的实现方式。