本文为设计模式系列第4篇,聚焦创建型模式中的单例模式,涵盖定义、原理、实际业务场景、优缺点、最佳实践及详细代码示例,适合系统学习与实战应用。
目录
1. 模式概述
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式主要用于控制资源的访问,确保资源的唯一性和一致性。通过私有构造方法和静态工厂方法,单例模式可以有效避免外部直接创建实例,常用于需要全局唯一对象的场景,防止资源浪费和状态不一致。
1.1 定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式通过私有化构造器、静态成员变量和静态工厂方法,确保系统中只存在一个该类的对象。它适用于需要全局唯一对象的场景,如配置管理、日志系统等,能够有效避免资源冲突和状态不一致。
1.2 目的
单例模式的核心目的是确保系统中某个类只有一个实例,避免重复创建带来的资源浪费。通过全局访问点,方便在系统各处获取同一个对象,保证状态一致性。此外,单例还能简化资源管理和配置维护,提升系统的健壮性。它不仅有助于统一管理有限资源,还能提升系统的可维护性和扩展性。
2. 使用场景
单例模式适用于以下场景:
- 资源共享:如配置管理、连接池、线程池等,多个模块需要共享同一份资源,单例模式可以避免重复创建和资源冲突。
- 全局状态:如计数器、日志记录、缓存管理等,全局状态需要被多个地方访问和修改,单例模式保证了数据的一致性和同步。
- 资源控制:如数据库连接、文件系统、设备管理等,通过单例模式集中管理资源,便于统一分配和回收,减少资源泄漏风险。
- 性能优化:如对象复用、内存优化、资源管理等,单例模式可以减少对象创建次数,提升系统性能,尤其适合重量级对象。
3. 优缺点分析
3.1 优点
- 资源控制:实例唯一,避免资源的重复分配和冲突,适合管理有限资源。统一的资源管理方式有助于系统的可维护性和扩展性。在多线程环境下,合理实现单例还能保证状态同步和一致性。
- 全局访问:单例对象可以被系统各处方便地访问,简化了依赖传递和对象管理。统一的全局入口有助于集中控制和监控,便于后期维护和调优。适合需要全局唯一状态或配置的场景,如日志、配置、缓存等。
- 延迟初始化:支持延迟加载,只有在真正需要时才创建实例,节省系统资源。这种按需分配的方式可以提升系统启动速度和运行效率。对于重量级对象,延迟初始化尤为重要,避免无谓的资源占用。
3.2 缺点
- 违反单一职责:单例类往往承担了过多的职责,既负责业务逻辑又负责实例管理,容易导致代码臃肿。全局访问点可能隐藏依赖关系,增加系统耦合,降低灵活性。单例难以进行单元测试和Mock,影响测试覆盖率和可维护性。
- 滥用风险:滥用单例会导致全局状态泛滥,增加调试和维护难度。隐式依赖使得代码难以理解和追踪,降低系统的可扩展性。建议仅在确有必要时使用单例,避免将其作为通用解决方案。
- 并发问题:在多线程环境下,单例实现不当容易引发线程安全问题,如重复创建、状态不一致等。加锁和同步会带来性能开销,影响系统吞吐量。需要合理选择实现方式,权衡安全性和性能。
4. 实际应用案例
- 配置管理:应用配置、系统设置、环境变量等,单例模式可集中管理和访问配置信息,保证全局一致。
- 资源管理:如数据库连接、线程池、缓存系统等,单例模式便于统一分配和回收资源,提升系统性能。
- 日志系统:日志记录、错误处理、性能监控等,单例模式可确保日志对象唯一,便于集中管理和分析。
- 设备控制:如打印机、扫描仪、网络设备等,单例模式可避免设备资源被多次初始化或冲突。
5. 结构与UML类图
@startuml
package "Singleton Pattern" #DDDDDD {
class Singleton {
- instance: Singleton
- Singleton()
+ getInstance(): Singleton
+ doSomething(): void
}
class Client
Singleton <.. Client : uses
}
@enduml
单例模式结构很简单,核心是私有构造器和静态实例获取方法。
6. 代码示例
6.1 基本结构
业务背景: 类加载时即创建实例,线程安全,适合对资源占用不敏感、全局频繁访问的场景。
// 饿汉式单例(线程安全)
public class Singleton {
// 静态成员变量,类加载时初始化
private static final Singleton instance = new Singleton();
// 私有构造方法,防止外部直接创建
private Singleton() {}
// 获取单例实例的全局访问点
public static Singleton getInstance() {
return instance;
}
}
// 懒汉式单例(线程不安全)
// 线程安全性:不安全,仅适用于单线程环境。多线程下可能创建多个实例。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// 懒汉式单例(线程安全)
// 线程安全性:安全,适合低并发场景。synchronized影响性能。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// 双重检查锁单例
// 线程安全性:安全,兼顾性能,推荐高并发场景。需配合volatile。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 静态内部类单例
// 线程安全性:安全,延迟加载,推荐大多数场景。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
// 枚举单例
// 线程安全性:安全,防止反射和序列化破坏,推荐极高安全要求场景。
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务逻辑
}
}
6.2 实际应用示例
// 配置管理器单例
public class ConfigurationManager {
// volatile保证多线程下可见性
private static volatile ConfigurationManager instance;
// 配置属性对象
private Properties properties;
// 私有构造方法,加载配置
private ConfigurationManager() {
loadConfiguration();
}
// 获取单例实例,双重检查锁保证线程安全和性能
public static ConfigurationManager getInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = new ConfigurationManager();
}
}
}
return instance;
}
// 加载配置文件
private void loadConfiguration() {
properties = new Properties();
try {
properties.load(new FileInputStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取配置项
public String getProperty(String key) {
return properties.getProperty(key);
}
// 设置配置项
public void setProperty(String key, String value) {
properties.setProperty(key, value);
}
// 保存配置到文件
public void saveConfiguration() {
try {
properties.store(new FileOutputStream("config.properties"), "Updated configuration");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 数据库连接池单例
public class DatabaseConnectionPool {
// volatile保证多线程下可见性
private static volatile DatabaseConnectionPool instance;
// 连接池列表
private List<Connection> connections;
// 最大连接数
private int maxConnections;
// 私有构造方法,初始化连接池
private DatabaseConnectionPool() {
maxConnections = 10;
connections = new ArrayList<>();
initializePool();
}
// 获取单例实例,双重检查锁保证线程安全
public static DatabaseConnectionPool getInstance() {
if (instance == null) {
synchronized (DatabaseConnectionPool.class) {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
}
}
return instance;
}
// 初始化连接池
private void initializePool() {
for (int i = 0; i < maxConnections; i++) {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
connections.add(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 获取连接,线程安全
public synchronized Connection getConnection() {
if (connections.isEmpty()) {
throw new RuntimeException("No available connections");
}
return connections.remove(0);
}
// 释放连接,线程安全
public synchronized void releaseConnection(Connection connection) {
if (connections.size() < maxConnections) {
connections.add(connection);
}
}
}
// 日志系统单例
public class Logger {
private static final Logger instance = new Logger(); // 饿汉式,线程安全
private Logger() {}
public static Logger getInstance() {
return instance;
}
public synchronized void log(String message) {
// 简化:实际可写入文件/数据库等
System.out.println("[LOG] " + message);
}
}
// 用法示例:Logger.getInstance().log("系统启动");
7. 测试用例
import org.junit.Test;
import static org.junit.Assert.*;
public class SingletonPatternTest {
@Test
public void testEagerSingleton() {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
assertSame(s1, s2);
}
@Test
public void testLazySingleton() {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
assertSame(s1, s2);
}
@Test
public void testEnumSingleton() {
assertSame(Singleton.INSTANCE, Singleton.INSTANCE);
}
}
8. 常见误区与反例
- 误区1:多线程下实现不安全,导致多个实例。
- 反例:未加锁的懒汉式单例在多线程下可能创建多个实例。
- 误区2:滥用单例,导致全局状态混乱。
- 单例应只用于确实需要全局唯一对象的场景。
- 误区3:单例类承担过多职责,变成"万能类"。
- 应保持单一职责,避免业务逻辑和实例管理混杂。
9. 最佳实践
- 推荐实现方式:优先使用静态内部类或枚举实现,保证线程安全和防止反射/序列化破坏。
- 职责单一:单例类应只负责自身的唯一性管理,避免承担过多业务逻辑。
- 线程安全:多线程环境下需确保单例实现的线程安全。
- 资源释放:如单例持有外部资源,应提供资源释放方法。
- 文档同步:保持文档、UML和代码示例一致,便于团队协作。
10. 参考资料与延伸阅读
- 《设计模式:可复用面向对象软件的基础》GoF
- Effective Java(中文版)
- https://refactoringguru.cn/design-patterns/singleton
- https://www.baeldung.com/java-singleton
本文为设计模式系列第4篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。