模式定义

单例模式是一种创建型设计模式,它能保证一个类只有一个实例,并提供一个访问该实例的全局节点。该模式的主要意图是确保一个类只创建一个实例,并提供一个全局访问点来访问该实例。

结构图

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);
    }
}

适用场景

优缺点

优点

  • 内存中只有一个实例,减少了内存开销
  • 避免对资源的多重占用
  • 设置全局访问点,优化和共享资源访问
  • 内存节约:由于单例模式在内存中只有一个实例,减少了内存开支
  • 避免频繁创建销毁:特别是那些创建和销毁比较耗时的对象,使用单例可以提升性能
  • 全局访问点:提供了全局访问点,可以严格控制客户对它的访问

缺点

  • 没有抽象层,扩展困难
  • 职责过重,在一定程度上违背单一职责原则
  • 在并发环境中需要考虑线程同步问题
  • 违背单一职责原则:一个类应该只关心内部逻辑,而不应该关心是否要单例以及创建过程
  • 难以扩展:单例模式一般不会作为父类被继承,因为子类也可能成为多例
  • 测试困难:单例模式很难进行单元测试,因为无法模拟多个实例的情况