一.单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,用于确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问,如配置文件读取、数据库连接、线程池等场景中非常有用。
1.实现要点
- 私有静态变量:保存类的唯一实例。
- 私有构造函数 :防止外部通过
new
关键字创建实例。 - 公共静态方法:提供全局访问点,返回类的唯一实例。如果实例不存在,则创建它。
2.常见的实现方式
懒汉式(线程不安全)
java
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
// 注意:这个实现在多线程环境下是不安全的
}
懒汉式(线程安全)
通过在getInstance()
方法上添加synchronized
关键字来保证线程安全,但这样会影响性能。
java
public class SingletonLazyThreadSafe {
private static SingletonLazyThreadSafe instance;
private SingletonLazyThreadSafe() {}
public static synchronized SingletonLazyThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonLazyThreadSafe();
}
return instance;
}
}
双重检查锁定(Double-Checked Locking)
这种方式既保证了线程安全,又提高了性能,但需要注意volatile
关键字的使用来防止指令重排序。
java
public class SingletonDoubleChecked {
private static volatile SingletonDoubleChecked instance;
private SingletonDoubleChecked() {}
public static SingletonDoubleChecked getInstance() {
if (instance == null) {
synchronized (SingletonDoubleChecked.class) {
if (instance == null) {
instance = new SingletonDoubleChecked();
}
}
}
return instance;
}
}
静态内部类
这种方式利用了classloader的机制来保证实例的唯一性,并且实现了懒加载。
java
public class SingletonStaticInner {
private SingletonStaticInner() {}
private static class SingletonHolder {
private static final SingletonStaticInner INSTANCE = new SingletonStaticInner();
}
public static final SingletonStaticInner getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
这是实现单例模式的最佳方法,它更简洁,自动支持序列化机制,绝对防止多次实例化。
java
public enum SingletonEnum {
INSTANCE;
public void someMethod() {
// 方法实现
}
}
// 使用
SingletonEnum.INSTANCE.someMethod();
3.总结
单例模式是一种简单但强大的设计模式,用于确保类只有一个实例。不同的实现方式有不同的特点和适用场景,需要根据实际需求来选择。枚举方式是推荐的首选实现方式,因为它既简洁又安全。
二.多例模式
多例模式(Multiton Pattern)是单例模式的一种变体,它允许一个类有多个实例,但实例的数量是有限且固定的。在多例模式中,通常会有一个枚举或者一个映射(如HashMap
)来管理这些有限的实例。每个实例都与一个唯一的标识符(如键或枚举值)相关联。
1.实现要点
- 私有静态映射:用于存储所有已创建的实例,键是唯一的标识符,值是对应的实例。
- 私有构造函数 :防止外部通过
new
关键字创建实例。 - 公共静态方法:根据传入的唯一标识符返回对应的实例。如果实例不存在,则创建它并存储在映射中。
2.示例(Java)
以下是一个使用HashMap
来实现多例模式的示例
java
import java.util.HashMap;
import java.util.Map;
public class Multiton {
// 私有静态映射,用于存储所有实例
private static final Map<KeyType, Multiton> instances = new HashMap<>();
// 私有构造函数
private Multiton() {}
// 公共静态方法,根据KeyType返回对应的实例
public static synchronized Multiton getInstance(KeyType key) {
Multiton instance = instances.get(key);
if (instance == null) {
instance = new Multiton();
instances.put(key, instance);
}
return instance;
}
// 假设的KeyType枚举,定义了所有可能的实例键
public enum KeyType {
INSTANCE1, INSTANCE2, INSTANCE3;
}
// 类的其他方法和属性...
}
// 使用示例
public class Main {
public static void main(String[] args) {
Multiton instance1 = Multiton.getInstance(Multiton.KeyType.INSTANCE1);
Multiton instance2 = Multiton.getInstance(Multiton.KeyType.INSTANCE2);
// instance1 和 instance2 是不同的实例
System.out.println(instance1 == instance2); // 输出 false
// 再次获取相同的实例
Multiton instance1Again = Multiton.getInstance(Multiton.KeyType.INSTANCE1);
// instance1 和 instance1Again 是相同的实例
System.out.println(instance1 == instance1Again); // 输出 true
}
}
3.注意事项
-
线程安全 :在多线程环境下,
getInstance
方法需要是同步的,以防止多个线程同时创建同一个键的实例。然而,同步可能会影响性能。如果实例的创建不是非常频繁,或者可以通过其他方式(如双重检查锁定)来优化同步,那么这种影响可能是可以接受的。 -
枚举与多例模式:虽然枚举通常用于实现单例模式,但直接使用枚举来实现多例模式可能不太直观,因为枚举的实例是在类加载时创建的,并且数量是固定的。然而,你可以通过组合使用枚举和映射(或类似结构)来间接实现多例模式,但这样做可能会使代码变得复杂。
-
性能考虑 :在多例模式中,由于实例的数量是有限的,因此通常不需要担心内存使用过多的问题。然而,如果实例的创建或销毁非常昂贵(例如,涉及大量资源分配或清理),则可能需要考虑使用对象池等更高级的技术来管理这些实例。
三.区别
1.例数量
- 单例模式:确保一个类仅有一个实例,并提供一个全局访问点。这意味着在整个应用程序的生命周期中,无论请求多少次,都只会创建和共享这一个实例。
- 多例模式:允许一个类有多个实例,但实例的数量是有限的。这意味着根据需求,可以创建多个实例,但实例的总数受到控制,不会无限制地增长。
2.实现方式
- 单例模式:通常通过私有静态变量来存储唯一的实例,并提供一个公共的静态方法来获取该实例。如果实例不存在,则创建它;如果已存在,则直接返回。
- 多例模式:通常使用一个集合(如HashMap)来存储多个实例,每个实例都有一个唯一的标识符(如ID或键值)。通过提供一个公共的静态方法,并传入标识符来获取对应的实例。如果实例不存在,则根据标识符创建新的实例,并将其添加到集合中;如果已存在,则直接返回。
3.应用场景
- 单例模式:适用于那些在整个系统中只需要一个实例的场景,如配置文件的读取、线程池、数据库连接池等。这些场景通常要求全局只有一个实例来管理资源或状态,以避免数据不一致或资源浪费。
- 多例模式:适用于那些需要多个实例但实例数量有限的场景,如缓存系统、状态机等。在这些场景中,每个实例可能代表不同的状态或配置,因此需要独立管理。多例模式可以限制实例数量,避免资源过度使用,并复用已有实例来提高系统性能。
4.优缺点
- 单例模式 :
- 优点:提供对唯一实例的受控访问,节约系统资源,提高性能。
- 缺点:可能导致全局状态的存在,增加代码的复杂性;难以追踪和理解的依赖关系;可能引发并发问题;阻碍扩展。
- 多例模式 :
- 优点:可以复用已有实例,避免重复创建对象,提高系统性能和资源利用率;可以灵活控制实例的生命周期。
- 缺点:实例数量固定,难以动态地增加或减少实例数量;难以对每个实例进行单独的测试;破坏封装性,使得代码难以维护和扩展;代码复杂度高,需要考虑线程安全、序列化等问题。