单例模式 (Singleton Pattern)
确保一个类只有一个实例,并提供一个全局访问点
模式定义
单例模式是一种创建型设计模式,它能保证一个类只有一个实例,并提供一个访问该实例的全局节点。该模式的主要意图是确保一个类只创建一个实例,并提供一个全局访问点来访问该实例。
结构图
graph TD
A[客户端] --> B((单例类))
B --> C{实例是否存在?}
C -->|是| D[返回现有实例]
C -->|否| E[创建新实例]
E --> F[保存实例]
F --> D
实现方式
饿汉式(线程安全)
在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快,线程安全。
public class EagerSingleton {
// 在类加载时就完成实例化
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() {}
// 提供全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
懒汉式(线程不安全)
在第一次调用getInstance方法时才实例化,实现了懒加载,但线程不安全。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重检查锁定(推荐)
既满足延迟加载,又满足线程安全,同时还能保持高性能。
public class DoubleCheckSingleton {
// volatile关键字确保多线程环境下的可见性
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance() {
// 第一次检查,避免不必要的同步
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
// 第二次检查,确保只创建一个实例
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
静态内部类(推荐)
利用JVM的类加载机制来保证线程安全,既实现了懒加载,又保证了线程安全。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
// 静态内部类
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举实现(最推荐)
不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("执行某些方法...");
}
}
// 使用方式
EnumSingleton.INSTANCE.doSomething();
经典案例
日志记录器
在应用程序中,通常只需要一个日志记录器实例来统一管理日志输出。
public class Logger {
private static volatile Logger instance;
private StringBuilder logData;
private Logger() {
logData = new StringBuilder();
}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
public void log(String message) {
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
logData.append("[").append(timestamp).append("] ").append(message).append("\n");
System.out.println("[" + timestamp + "] " + message);
}
public String getLogData() {
return logData.toString();
}
}
配置管理器
应用程序的配置信息通常在整个应用生命周期内只需要一份实例。
public class ConfigManager {
private static volatile ConfigManager instance;
private java.util.Map configMap;
private ConfigManager() {
configMap = new java.util.HashMap<>();
// 初始化默认配置
loadDefaultConfig();
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
private void loadDefaultConfig() {
configMap.put("appName", "MyApplication");
configMap.put("version", "1.0.0");
configMap.put("debug", "true");
}
public String getConfig(String key) {
return configMap.get(key);
}
public void setConfig(String key, String value) {
configMap.put(key, value);
}
}
适用场景
- 需要频繁实例化然后销毁的对象
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗等
- 控制资源的情况下,方便资源之间的互相通信
- 如配置信息、数据库连接池、线程池、日志对象等需要在应用程序中共享的资源
- 如打印机、显卡等硬件资源的管理,确保同一时刻只有一个程序在使用
- 如计数器、缓存管理器等需要全局访问的对象
优缺点
优点
- 内存中只有一个实例,减少了内存开销
- 避免对资源的多重占用
- 设置全局访问点,优化和共享资源访问
- 内存节约:由于单例模式在内存中只有一个实例,减少了内存开支
- 避免频繁创建销毁:特别是那些创建和销毁比较耗时的对象,使用单例可以提升性能
- 全局访问点:提供了全局访问点,可以严格控制客户对它的访问
缺点
- 没有抽象层,扩展困难
- 职责过重,在一定程度上违背单一职责原则
- 在并发环境中需要考虑线程同步问题
- 违背单一职责原则:一个类应该只关心内部逻辑,而不应该关心是否要单例以及创建过程
- 难以扩展:单例模式一般不会作为父类被继承,因为子类也可能成为多例
- 测试困难:单例模式很难进行单元测试,因为无法模拟多个实例的情况