单例模式(Singleton Pattern)是一种常见的设计模式,属于创建型设计模式(Creational Design Patterns),旨在确保一个类在整个系统中只有一个实例,并且提供一个全局的访问点来获取这个实例。
具体来说,单例模式通过限制类的实例化次数,确保系统中只有一个该类的对象存在。这通常用于以下场景:
- 当系统需要共享一个资源,比如数据库连接、日志记录器或配置管理器时。
- 确保一个类的实例被全局共享,避免资源浪费。
为什么要使用单例模式呢(什么时候应该使用单例模式)
单例模式的使用场景通常包括以下几种情况:
节约资源:当多个模块或组件需要共享同一个资源时。使用单例模式可以确保资源的一致性,避免重复创建和销毁实例。例如,数据库连接池、线程池等。
全局控制:保证只有一个实例,这样就可以严格的控制客户怎样访问它以及何时访问它,简单的说就是对唯一实例的受控访问。
控制实例数量:在某些情况下,系统中某个类的实例只能有一个,否则会导致逻辑错误或资源冲突。例如,配置管理器、日志记录器等。
懒加载:单例模式可以延迟实例的创建,直到第一次使用时才进行初始化,从而节省系统资源。
单例模式的基本要求
私有的静态实例变量:类内部维护一个静态的实例变量,用于保存唯一的实例。
私有构造函数:防止外部通过 new 关键字创建实例,确保实例的唯一性。
公有静态访问方法:提供一个静态的公共方法(通常命名为 getInstance),用于获取该类的唯一实例。
线程安全:在多线程环境下,确保单例模式的实现是线程安全的,避免多个线程同时创建多个实例。
单例模式的实现方法
懒汉式(Lazy Initialization)
懒汉式单例模式在第一次调用 getInstance 方法时才创建实例。优点是延迟初始化,节省资源;缺点是在多线程环境下需要额外的同步机制来保证线程安全。1
2
3
4
5
6
7
8
9
10
11
12public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}饿汉式(Eager Initialization)
饿汉式单例模式在类加载时就创建实例。优点是实现简单,线程安全;缺点是如果实例一直没有被使用,会造成资源浪费。1
2
3
4
5
6
7
8
9public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}双重检查锁(Double-Checked Locking)
双重检查锁单例模式结合了懒汉式和饿汉式的优点,既实现了延迟初始化,又保证了线程安全。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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;
}
}
单例模式的优缺点
优点:
- 控制实例数量:确保系统中只有一个实例,避免资源浪费。
- 全局访问点:提供了一个全局的访问点,方便其他模块获取实例。
- 延迟初始化:可以在需要时才创建实例,节省系统资源。
缺点:
- 难以扩展:单例模式通常难以扩展,因为它的实例是全局唯一的,扩展可能会导致设计复杂化。
- 违反单一职责原则:单例类通常既负责业务逻辑,又负责实例管理,违反了单一职责原则。
- 测试困难:由于单例模式的全局性,单元测试时可能会遇到困难,尤其是在需要模拟或替换单例实例时。
单例模式的应用场景
单例模式的应用场景非常广泛,在以下几个方面有着广泛的应用:
- 配置管理:系统中通常只需要一个配置管理器来读取和保存配置信息。
- 日志记录:日志记录器通常只需要一个实例来记录系统的运行日志。
- 数据库连接池:数据库连接池通常只需要一个实例来管理数据库连接。
- 缓存系统:缓存系统通常只需要一个实例来管理缓存数据。
- 线程池:线程池通常只需要一个实例来管理线程资源。
- 硬件访问:某些硬件资源(如打印机、扫描仪等)通常只需要一个实例来管理访问。