单例和多例是软件设计中对象实例管理的两种模式,主要区别在于对象的实例数量和管理方式。
1. 概念
单例模式 (Singleton)
单例模式确保一个类在整个应用运行期间只有一个实例,并提供一个全局访问点来访问该实例。
- 特点:
- 全局唯一。
- 节省资源,尤其是需要频繁创建和销毁对象的场景(如数据库连接池)。
- 提供统一的访问接口。
多例模式 (Prototype/Multiton)
多例模式允许一个类在运行时存在多个实例,每个实例可能有不同的状态,通常通过工厂模式或其他方式管理。
- 特点:
- 每次需要时都可以创建新的实例。
- 每个实例独立,不会相互影响。
- 适合短生命周期对象,或者需要多个不同配置对象的场景。
2. 单例和多例的区别
特点 | 单例模式 | 多例模式 |
---|---|---|
实例数量 | 全局唯一,整个应用程序共享一个实例。 | 可以有多个实例,每次获取可以不同。 |
内存消耗 | 只创建一次,节省内存和资源。 | 每次创建新实例,内存消耗可能较多。 |
适用场景 | 配置管理类、线程池、日志管理、数据库连接池等。 | 游戏角色、图形对象、Web 请求处理等。 |
并发问题 | 需要考虑多线程环境下的实例同步问题。 | 一般无需担心,但管理实例的数量可能需要关注。 |
实现复杂度 | 实现较复杂,尤其是线程安全的单例实现。 | 实现简单,实例管理交由外部处理即可。 |
3. 单例的实现
单例模式的实现有多种方式,根据是否需要线程安全和延迟加载,可以选择不同方案。
(1) 饿汉式单例
- 特点: 类加载时就创建实例,线程安全,但可能造成资源浪费。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return INSTANCE;
}
}
(2) 懒汉式单例
- 特点: 在第一次调用时创建实例,节省资源,但需要考虑线程安全。
线程不安全版本:
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全版本 (双重检查锁):
public class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
(3) 静态内部类
- 特点: 延迟加载,线程安全,推荐使用。
public class Singleton {
private Singleton() { }
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4. 多例的实现
多例模式通常通过工厂模式或容器管理实现。以下是常见的实现方式。
(1) 工厂模式
根据需要创建不同实例。
public class Multiton {
private static final Map<String, Multiton> instances = new HashMap<>();
private final String value;
private Multiton(String value) {
this.value = value;
}
public static Multiton getInstance(String key) {
return instances.computeIfAbsent(key, k -> new Multiton(k));
}
public String getValue() {
return value;
}
}
(2) Spring 的原型模式 (Prototype Scope)
在 Spring 框架中,通过配置 @Scope
注解实现多例。
@Component
@Scope("prototype")
public class PrototypeBean {
public PrototypeBean() {
System.out.println("PrototypeBean 实例化");
}
}
每次从容器获取时都会创建一个新实例:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
System.out.println(bean1 == bean2); // 输出 false
5. 应用场景总结
单例适合场景
- 全局唯一对象:配置管理器、线程池、日志记录器、数据库连接池。
- 高性能场景:频繁访问的对象需要复用,避免多次创建和销毁。
多例适合场景
- 每次需要不同实例:Web 请求处理器、图形对象、游戏中多角色管理。
- 状态独立的对象:每个实例有不同的配置或数据。
总结
- 单例强调的是全局唯一性,适合资源共享或需要集中管理的场景。
- 多例注重实例的独立性和多样性,适合需要频繁创建对象或分开管理状态的场景。
- 选择哪种模式要根据具体的业务需求和资源管理策略来决定。