设计模式手册005:单例模式 - 唯一实例的优雅实现

本文是「设计模式手册」系列第005篇,我们将深入探讨单例模式,这种模式确保一个类只有一个实例,并提供全局访问点,是设计模式中最简单也最容易用错的一种。

1. 场景:我们为何需要单例模式?

在软件开发中,有些对象我们只需要一个实例,比如:

  • 配置管理器:整个应用只需要一份配置
  • 数据库连接池:避免重复创建连接
  • 日志记录器:统一记录日志
  • 线程池:管理所有线程资源
  • 缓存管理器:全局缓存共享
  • 设备驱动对象:物理设备只能有一个访问点

错误做法的代价

public class ConfigManager {
private Map<String, String> configs = new HashMap<>();
  public ConfigManager() {
  // 加载配置文件的昂贵操作
  loadConfigFromFile();
  }
  private void loadConfigFromFile() {
  // 模拟耗时操作
  try {
  Thread.sleep(1000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  // 读取配置文件...
  configs.put("db.url", "jdbc:mysql://localhost:3306/test");
  configs.put("db.username", "root");
  configs.put("app.name", "MyApplication");
  }
  public String getConfig(String key) {
  return configs.get(key);
  }
  }
  // 问题:每次使用都创建新实例
  public class Application {
  public void start() {
  // 每次调用都创建新的ConfigManager,重复加载配置
  String dbUrl = new ConfigManager().getConfig("db.url");
  String appName = new ConfigManager().getConfig("app.name");
  // 创建了多个实例,浪费资源,且配置可能不一致
  }
  }

这种实现的痛点

  • 资源浪费:重复创建相同对象
  • 性能问题:初始化操作重复执行
  • 状态不一致:多个实例可能状态不同
  • 内存泄漏:无用的对象占用内存

2. 单例模式:定义与本质

2.1 模式定义

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。

2.2 核心要素

public class Singleton {
// 1. 私有静态实例
private static Singleton instance;
// 2. 私有构造函数
private Singleton() {
// 防止外部通过new创建实例
}
// 3. 公共静态访问方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

3. 深入理解:单例模式的演进之路

3.1 第一重:饿汉式单例

特点:类加载时就创建实例,线程安全但可能浪费资源

public class EagerSingleton {
// 类加载时立即创建实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
System.out.println("EagerSingleton实例被创建");
// 模拟初始化耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void showMessage() {
System.out.println("Hello from EagerSingleton!");
}
}
// 测试
public class EagerSingletonTest {
public static void main(String[] args) {
// 即使没有调用getInstance,实例也会在类加载时创建
System.out.println("程序启动");
// 实例已经在类加载时创建
EagerSingleton.getInstance().showMessage();
}
}

3.2 第二重:懒汉式单例(线程不安全)

特点:延迟加载,但多线程环境下可能创建多个实例

public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
System.out.println("LazySingleton实例被创建");
}
// 线程不安全!
public static LazySingleton getInstance() {
if (instance == null) {
// 多个线程可能同时进入这里
instance = new LazySingleton();
}
return instance;
}
}
// 演示线程安全问题
public class ThreadUnsafeTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
LazySingleton singleton = LazySingleton.getInstance();
System.out.println("线程" + Thread.currentThread().getId() + "获取实例: " + singleton);
});
}
executor.shutdown();
// 可能输出多个"LazySingleton实例被创建"
}
}

3.3 第三重:线程安全的懒汉式

3.3.1 方法同步
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {
System.out.println("SynchronizedSingleton实例被创建");
}
// 线程安全,但性能较差
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
3.3.2 双重检查锁定(DCL)
public class DoubleCheckedLockingSingleton {
// 使用volatile防止指令重排序
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {
System.out.println("DoubleCheckedLockingSingleton实例被创建");
}
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) { // 第二次检查
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}

3.4 第四重:静态内部类实现

推荐用法:兼顾延迟加载和线程安全

public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
System.out.println("StaticInnerClassSingleton实例被创建");
}
// 静态内部类在第一次被引用时才会加载
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

3.5 第五重:枚举单例

最佳实践:Effective Java作者Joshua Bloch推荐的方式

public enum EnumSingleton {
INSTANCE;
// 可以添加方法
public void doSomething() {
System.out.println("枚举单例方法执行");
}
// 可以持有状态
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
// 使用
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton singleton = EnumSingleton.INSTANCE;
singleton.setData("Hello World");
singleton.doSomething();
System.out.println(singleton.getData());
}
}

4. 实战案例:完整的配置管理器

/**
* 配置管理器单例
* 使用静态内部类实现,兼顾线程安全和延迟加载
*/
public class ConfigManager {
private final Properties properties;
private final String configFile = "application.properties";
// 私有构造函数
private ConfigManager() {
properties = new Properties();
loadConfig();
}
// 静态内部类
private static class SingletonHolder {
private static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return SingletonHolder.INSTANCE;
}
private void loadConfig() {
try (InputStream input = getClass().getClassLoader().getResourceAsStream(configFile)) {
if (input == null) {
System.out.println("找不到配置文件: " + configFile);
return;
}
properties.load(input);
System.out.println("配置文件加载完成");
} catch (IOException e) {
System.err.println("加载配置文件失败: " + e.getMessage());
}
}
public String getProperty(String key) {
return properties.getProperty(key);
}
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getIntProperty(String key, int defaultValue) {
String value = properties.getProperty(key);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
System.err.println("配置项 " + key + " 不是有效的整数: " + value);
}
}
return defaultValue;
}
public boolean getBooleanProperty(String key, boolean defaultValue) {
String value = properties.getProperty(key);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
// 重新加载配置
public void reload() {
properties.clear();
loadConfig();
}
// 获取所有配置键
public Set<String> getAllKeys() {
  return properties.stringPropertyNames();
  }
  }
  // 使用示例
  public class ConfigManagerTest {
  public static void main(String[] args) {
  ConfigManager config = ConfigManager.getInstance();
  // 获取配置
  String dbUrl = config.getProperty("db.url", "jdbc:mysql://localhost:3306/default");
  int maxConnections = config.getIntProperty("db.max.connections", 10);
  boolean debugMode = config.getBooleanProperty("app.debug", false);
  System.out.println("数据库URL: " + dbUrl);
  System.out.println("最大连接数: " + maxConnections);
  System.out.println("调试模式: " + debugMode);
  // 显示所有配置
  System.out.println("所有配置项:");
  config.getAllKeys().forEach(key ->
  System.out.println(key + " = " + config.getProperty(key))
  );
  }
  }

5. Spring框架中的单例模式

Spring容器默认使用单例作用域,但不同于传统的单例模式:

5.1 Spring的单例Bean

@Component
@Scope("singleton") // 默认就是singleton,可以省略
public class DatabaseConnectionPool {
private final List<Connection> connections = new ArrayList<>();
  private final int maxSize = 10;
  @PostConstruct
  public void init() {
  System.out.println("初始化数据库连接池");
  // 初始化连接
  for (int i = 0; i < maxSize; i++) {
  connections.add(createConnection());
  }
  }
  @PreDestroy
  public void destroy() {
  System.out.println("关闭数据库连接池");
  connections.forEach(this::closeConnection);
  connections.clear();
  }
  public Connection getConnection() {
  // 获取连接逻辑
  return connections.isEmpty() ? createConnection() : connections.remove(0);
  }
  public void releaseConnection(Connection connection) {
  if (connections.size() < maxSize) {
  connections.add(connection);
  } else {
  closeConnection(connection);
  }
  }
  private Connection createConnection() {
  // 创建数据库连接
  return null; // 实际实现
  }
  private void closeConnection(Connection connection) {
  // 关闭连接
  }
  }
  // 使用
  @Service
  public class UserService {
  private final DatabaseConnectionPool connectionPool;
  @Autowired
  public UserService(DatabaseConnectionPool connectionPool) {
  this.connectionPool = connectionPool;
  }
  public void processUserData() {
  Connection connection = connectionPool.getConnection();
  try {
  // 使用连接处理数据
  } finally {
  connectionPool.releaseConnection(connection);
  }
  }
  }

5.2 Spring的单例注册表

@Component
public class SingletonRegistry {
private final Map<String, Object> singletons = new ConcurrentHashMap<>();
  public void registerSingleton(String name, Object singleton) {
  if (singletons.containsKey(name)) {
  throw new IllegalStateException("单例 '" + name + "' 已存在");
  }
  singletons.put(name, singleton);
  }
  @SuppressWarnings("unchecked")
  public <T> T getSingleton(String name, Class<T> requiredType) {
    Object singleton = singletons.get(name);
    if (singleton == null) {
    return null;
    }
    if (!requiredType.isInstance(singleton)) {
    throw new IllegalArgumentException("单例 '" + name + "' 不是 " + requiredType.getName() + " 类型");
    }
    return (T) singleton;
    }
    public String[] getSingletonNames() {
    return singletons.keySet().toArray(new String[0]);
    }
    public int getSingletonCount() {
    return singletons.size();
    }
    }
    // 配置类
    @Configuration
    public class SingletonConfig {
    @Bean
    public SingletonRegistry singletonRegistry() {
    return new SingletonRegistry();
    }
    @Bean
    public CacheManager cacheManager(SingletonRegistry registry) {
    CacheManager cacheManager = new CacheManager();
    registry.registerSingleton("cacheManager", cacheManager);
    return cacheManager;
    }
    }

6. 单例模式的进阶用法

6.1 线程局部单例

/**
* 线程局部单例:每个线程有自己的单例实例
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
  ThreadLocal.withInitial(ThreadLocalSingleton::new);
  private ThreadLocalSingleton() {
  System.out.println("创建ThreadLocalSingleton实例,线程: " + Thread.currentThread().getName());
  }
  public static ThreadLocalSingleton getInstance() {
  return threadLocalInstance.get();
  }
  public void doSomething() {
  System.out.println("执行操作,线程: " + Thread.currentThread().getName());
  }
  // 重要:使用完毕后需要清理,防止内存泄漏
  public static void remove() {
  threadLocalInstance.remove();
  }
  }
  // 测试
  public class ThreadLocalSingletonTest {
  public static void main(String[] args) {
  ExecutorService executor = Executors.newFixedThreadPool(3);
  for (int i = 0; i < 6; i++) {
  final int taskId = i;
  executor.execute(() -> {
  ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
  singleton.doSomething();
  // 模拟任务执行
  try {
  Thread.sleep(1000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  // 任务完成后清理
  ThreadLocalSingleton.remove();
  });
  }
  executor.shutdown();
  }
  }

6.2 单例对象池

/**
* 单例对象池:管理有限数量的单例对象
*/
public class SingletonObjectPool<T> {
  private final Class<T> objectClass;
    private final BlockingQueue<T> objectPool;
      private final int maxSize;
      private final AtomicInteger createdCount = new AtomicInteger(0);
      public SingletonObjectPool(Class<T> objectClass, int maxSize) {
        this.objectClass = objectClass;
        this.maxSize = maxSize;
        this.objectPool = new ArrayBlockingQueue<>(maxSize);
          }
          public T borrowObject() throws Exception {
          T object = objectPool.poll();
          if (object != null) {
          return object;
          }
          if (createdCount.get() < maxSize) {
          synchronized (this) {
          if (createdCount.get() < maxSize) {
          object = objectClass.newInstance();
          createdCount.incrementAndGet();
          return object;
          }
          }
          }
          // 等待其他对象归还
          return objectPool.take();
          }
          public void returnObject(T object) {
          if (object != null) {
          objectPool.offer(object);
          }
          }
          public int getAvailableCount() {
          return objectPool.size();
          }
          public int getCreatedCount() {
          return createdCount.get();
          }
          }
          // 使用示例
          public class DatabaseConnection {
          private final String connectionId;
          public DatabaseConnection() {
          this.connectionId = "Connection-" + UUID.randomUUID().toString().substring(0, 8);
          System.out.println("创建数据库连接: " + connectionId);
          }
          public void executeQuery(String sql) {
          System.out.println(connectionId + " 执行查询: " + sql);
          }
          public String getConnectionId() {
          return connectionId;
          }
          }
          // 测试对象池
          public class ObjectPoolTest {
          public static void main(String[] args) throws Exception {
          SingletonObjectPool<DatabaseConnection> pool =
            new SingletonObjectPool<>(DatabaseConnection.class, 3);
              ExecutorService executor = Executors.newFixedThreadPool(5);
              for (int i = 0; i < 10; i++) {
              final int taskId = i;
              executor.execute(() -> {
              try {
              DatabaseConnection connection = pool.borrowObject();
              System.out.println("任务 " + taskId + " 获取连接: " + connection.getConnectionId());
              // 模拟数据库操作
              connection.executeQuery("SELECT * FROM users WHERE id = " + taskId);
              Thread.sleep(500);
              // 归还连接
              pool.returnObject(connection);
              System.out.println("任务 " + taskId + " 归还连接: " + connection.getConnectionId());
              } catch (Exception e) {
              e.printStackTrace();
              }
              });
              }
              executor.shutdown();
              executor.awaitTermination(10, TimeUnit.SECONDS);
              System.out.println("最终可用连接数: " + pool.getAvailableCount());
              System.out.println("总共创建连接数: " + pool.getCreatedCount());
              }
              }

7. 单例模式的陷阱与解决方案

7.1 反射攻击

public class ReflectionSafeSingleton {
private static volatile ReflectionSafeSingleton instance;
private static boolean initialized = false;
private ReflectionSafeSingleton() {
// 防止反射攻击
if (initialized) {
throw new RuntimeException("单例实例已存在,不能通过反射创建");
}
initialized = true;
System.out.println("ReflectionSafeSingleton实例被创建");
}
public static ReflectionSafeSingleton getInstance() {
if (instance == null) {
synchronized (ReflectionSafeSingleton.class) {
if (instance == null) {
instance = new ReflectionSafeSingleton();
}
}
}
return instance;
}
}
// 反射攻击测试
public class ReflectionAttackTest {
public static void main(String[] args) throws Exception {
// 正常获取实例
ReflectionSafeSingleton instance1 = ReflectionSafeSingleton.getInstance();
// 尝试通过反射创建新实例
try {
Constructor<ReflectionSafeSingleton> constructor =
  ReflectionSafeSingleton.class.getDeclaredConstructor();
  constructor.setAccessible(true);
  ReflectionSafeSingleton instance2 = constructor.newInstance();
  System.out.println("反射攻击成功");
  } catch (Exception e) {
  System.out.println("反射攻击被阻止: " + e.getMessage());
  }
  }
  }

7.2 序列化攻击

public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton() {
System.out.println("SerializableSingleton实例被创建");
}
public static SerializableSingleton getInstance() {
return INSTANCE;
}
// 防止序列化破坏单例
protected Object readResolve() {
return INSTANCE;
}
}
// 序列化攻击测试
public class SerializationAttackTest {
public static void main(String[] args) throws Exception {
SerializableSingleton instance1 = SerializableSingleton.getInstance();
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(instance1);
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
SerializableSingleton instance2 = (SerializableSingleton) ois.readObject();
ois.close();
System.out.println("实例1: " + instance1);
System.out.println("实例2: " + instance2);
System.out.println("是否是同一个实例: " + (instance1 == instance2));
}
}

8. 单例模式 vs 其他模式

8.1 单例模式 vs 静态类

  • 单例模式:可以继承、实现接口、延迟加载、支持状态
  • 静态类:只有静态方法,不能继承,类加载时就初始化

8.2 单例模式 vs 工厂模式

  • 单例模式:确保只有一个实例
  • 工厂模式:负责创建对象,不限制实例数量

8.3 单例模式 vs 享元模式

  • 单例模式:一个类只有一个实例
  • 享元模式:一个类有多个实例,但共享相同的内在状态

9. 总结与思考

9.1 单例模式的优点

  1. 严格控制实例数量:确保全局只有一个实例
  2. 全局访问点:方便其他对象访问
  3. 节省资源:避免重复创建相同对象
  4. 状态共享:所有使用者共享相同状态

9.2 单例模式的缺点

  1. 违反单一职责原则:同时负责创建和管理实例
  2. 难以测试:全局状态使得单元测试困难
  3. 隐藏依赖关系:使用单例的类依赖关系不明确
  4. 可能成为上帝对象:过度使用导致单例类过于庞大

9.3 设计思考

单例模式的本质是**“受控的全局状态”**。它通过限制实例化来确保状态的一致性,但这也带来了测试和维护的挑战。

深入思考的角度

“单例模式看似简单,实则暗藏玄机。它需要在简洁性和灵活性之间找到平衡,在确保唯一性的同时,还要考虑线程安全、序列化、反射攻击等各种边界情况。”

在实际应用中,单例模式有很多需要注意的地方:

  • Spring的Singleton作用域与经典单例的区别
  • 单例在分布式环境下的挑战
  • 单例与依赖注入的配合使用
  • 如何避免单例成为瓶颈

最佳实践建议

  1. 优先使用枚举或静态内部类实现单例
  2. 考虑使用依赖注入框架管理单例
  3. 避免在单例中保存可变状态
  4. 为单例编写适当的销毁方法
  5. 在多线程环境下充分测试

使用场景判断

  • 适合:线程池、缓存、配置、日志、设备访问
  • 不适合:需要测试的业务逻辑、可能变化的状态管理、需要多态的场景

下一篇预告:设计模式手册006 - 建造者模式:如何优雅地构建复杂对象?


版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。