设计模式之:单例模式
文章目录
- 什么是单例模式?
- 核心思想
- 模式结构
- 单例模式的多种实现方式
- 1. 饿汉式单例(Eager Initialization)
- 2. 懒汉式单例(Lazy Initialization)
- 3. 双重检查锁单例(Double-Checked Locking)
- 4. 静态内部类单例(Holder Pattern)
- 5. 枚举单例(Enum Singleton)
- 完整示例:配置管理器单例
- 1. 配置管理器单例类
- 2. 数据库连接池单例
- 3. 客户端使用示例
- 单例模式的优点
- 1. 严格控制实例数量
- 2. 全局访问点
- 3. 延迟初始化
- 单例模式的缺点
- 1. 违反单一职责原则
- 2. 难以测试
- 适用场景
- 最佳实践
- 1. 优先使用枚举或静态内部类实现
- 2. 考虑线程安全
- 3. 防止反射和反序列化破坏
- 总结
什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式是设计模式中最简单但也是最容易误用的模式之一。
核心思想
单例模式的核心思想是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 它通过控制对象的创建过程,确保在整个应用程序中只存在一个实例。
模式结构
单例模式包含一个核心角色:
- 单例类(Singleton):负责创建自己的唯一实例,并提供访问该实例的全局方法
单例模式的多种实现方式
1. 饿汉式单例(Eager Initialization)
/*** 饿汉式单例 - 线程安全* 在类加载时就完成实例化,避免了多线程同步问题*/
public class EagerSingleton {// 在类加载时立即实例化private static final EagerSingleton INSTANCE = new EagerSingleton();// 私有构造函数,防止外部实例化private EagerSingleton() {// 防止通过反射创建实例if (INSTANCE != null) {throw new RuntimeException("单例对象已被创建,禁止通过反射实例化");}System.out.println("饿汉式单例实例化完成");}// 全局访问点public static EagerSingleton getInstance() {return INSTANCE;}// 业务方法public void showMessage() {System.out.println("Hello from EagerSingleton!");}// 防止反序列化破坏单例private Object readResolve() {return INSTANCE;}
}
2. 懒汉式单例(Lazy Initialization)
/*** 懒汉式单例 - 基础版本(线程不安全)* 在第一次调用getInstance时才创建实例*/
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {System.out.println("懒汉式单例实例化完成");}// 线程不安全的实现public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}public void showMessage() {System.out.println("Hello from LazySingleton!");}
}/*** 懒汉式单例 - 线程安全版本(同步方法)* 通过synchronized保证线程安全,但性能较差*/
public class ThreadSafeLazySingleton {private static ThreadSafeLazySingleton instance;private ThreadSafeLazySingleton() {System.out.println("线程安全懒汉式单例实例化完成");}// 使用synchronized保证线程安全,但每次访问都会同步,性能低public static synchronized ThreadSafeLazySingleton getInstance() {if (instance == null) {instance = new ThreadSafeLazySingleton();}return instance;}public void showMessage() {System.out.println("Hello from ThreadSafeLazySingleton!");}
}
3. 双重检查锁单例(Double-Checked Locking)
/*** 双重检查锁单例 - 高性能线程安全版本* 既保证了线程安全,又提高了性能*/
public class DoubleCheckedLockingSingleton {// 使用volatile禁止指令重排序,保证可见性private static volatile DoubleCheckedLockingSingleton instance;private DoubleCheckedLockingSingleton() {System.out.println("双重检查锁单例实例化完成");}public static DoubleCheckedLockingSingleton getInstance() {// 第一次检查,避免不必要的同步if (instance == null) {// 同步代码块synchronized (DoubleCheckedLockingSingleton.class) {// 第二次检查,确保只有一个线程创建实例if (instance == null) {instance = new DoubleCheckedLockingSingleton();}}}return instance;}public void showMessage() {System.out.println("Hello from DoubleCheckedLockingSingleton!");}
}
4. 静态内部类单例(Holder Pattern)
/*** 静态内部类单例 - 推荐使用的实现方式* 结合了饿汉式的线程安全和懒汉式的延迟加载*/
public class InnerClassSingleton {private InnerClassSingleton() {System.out.println("静态内部类单例实例化完成");}// 静态内部类,在第一次被引用时才会加载private static class SingletonHolder {private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();}public static InnerClassSingleton getInstance() {return SingletonHolder.INSTANCE;}public void showMessage() {System.out.println("Hello from InnerClassSingleton!");}// 防止反序列化破坏单例private Object readResolve() {return SingletonHolder.INSTANCE;}
}
5. 枚举单例(Enum Singleton)
/*** 枚举单例 - Joshua Bloch推荐的方式* Effective Java作者推荐的单例实现方式,绝对防止多实例*/
public enum EnumSingleton {INSTANCE;// 枚举的构造函数默认就是private的EnumSingleton() {System.out.println("枚举单例实例化完成");}// 业务方法public void showMessage() {System.out.println("Hello from EnumSingleton!");}// 可以添加其他业务方法public void doBusinessLogic() {System.out.println("执行业务逻辑...");}
}
完整示例:配置管理器单例
让我们通过一个完整的配置管理器示例来展示单例模式的实际应用。
1. 配置管理器单例类
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;/*** 配置管理器单例 - 实际应用示例* 在整个应用中只需要一个配置管理器实例*/
public class ConfigurationManager {// 使用静态内部类实现单例private static class Holder {private static final ConfigurationManager INSTANCE = new ConfigurationManager();}private final Map<String, String> configMap;private final Properties properties;private boolean initialized = false;private ConfigurationManager() {this.configMap = new HashMap<>();this.properties = new Properties();System.out.println("配置管理器单例创建完成");loadDefaultConfig();}public static ConfigurationManager getInstance() {return Holder.INSTANCE;}/*** 加载默认配置*/private void loadDefaultConfig() {// 模拟加载默认配置configMap.put("app.name", "MyApplication");configMap.put("app.version", "1.0.0");configMap.put("database.url", "jdbc:mysql://localhost:3306/mydb");configMap.put("database.username", "admin");configMap.put("server.port", "8080");configMap.put("log.level", "INFO");initialized = true;System.out.println("默认配置加载完成");}/*** 获取配置值*/public String getConfig(String key) {if (!initialized) {throw new IllegalStateException("配置管理器未初始化");}return configMap.get(key);}/*** 获取配置值,带默认值*/public String getConfig(String key, String defaultValue) {String value = getConfig(key);return value != null ? value : defaultValue;}/*** 设置配置值*/public void setConfig(String key, String value) {if (!initialized) {throw new IllegalStateException("配置管理器未初始化");}configMap.put(key, value);System.out.println("配置已更新: " + key + " = " + value);}/*** 检查配置是否存在*/public boolean containsConfig(String key) {return configMap.containsKey(key);}/*** 获取所有配置*/public Map<String, String> getAllConfigs() {return new HashMap<>(configMap); // 返回副本保护内部数据}/*** 重新加载配置*/public void reloadConfig() {configMap.clear();loadDefaultConfig();System.out.println("配置重新加载完成");}/*** 打印所有配置*/public void printAllConfigs() {System.out.println("\n=== 当前所有配置 ===");configMap.forEach((key, value) -> System.out.printf("%-20s: %s%n", key, value));System.out.println("===================\n");}
}
2. 数据库连接池单例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** 数据库连接池单例 - 另一个实际应用示例*/
public class DatabaseConnectionPool {private static volatile DatabaseConnectionPool instance;private final List<Connection> connectionPool;private final List<Connection> usedConnections;private static final int INITIAL_POOL_SIZE = 10;private static final int MAX_POOL_SIZE = 20;private final String url;private final String user;private final String password;private DatabaseConnectionPool() {this.connectionPool = new ArrayList<>();this.usedConnections = new ArrayList<>();// 从配置管理器获取数据库配置ConfigurationManager config = ConfigurationManager.getInstance();this.url = config.getConfig("database.url");this.user = config.getConfig("database.username");this.password = config.getConfig("database.password", "defaultpass");initializePool();System.out.println("数据库连接池初始化完成,初始连接数: " + INITIAL_POOL_SIZE);}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 < INITIAL_POOL_SIZE; i++) {try {Connection connection = createConnection();connectionPool.add(connection);} catch (SQLException e) {System.err.println("创建数据库连接失败: " + e.getMessage());}}}private Connection createConnection() throws SQLException {// 模拟创建数据库连接System.out.println("创建新的数据库连接");return DriverManager.getConnection(url, user, password);}public synchronized Connection getConnection() {if (connectionPool.isEmpty()) {if (usedConnections.size() < MAX_POOL_SIZE) {try {Connection connection = createConnection();usedConnections.add(connection);return connection;} catch (SQLException e) {throw new RuntimeException("无法创建新连接", e);}} else {throw new RuntimeException("连接池已满,无法获取连接");}}Connection connection = connectionPool.remove(connectionPool.size() - 1);usedConnections.add(connection);return connection;}public synchronized boolean releaseConnection(Connection connection) {connectionPool.add(connection);return usedConnections.remove(connection);}public synchronized void shutdown() {for (Connection connection : connectionPool) {try {connection.close();} catch (SQLException e) {System.err.println("关闭连接失败: " + e.getMessage());}}for (Connection connection : usedConnections) {try {connection.close();} catch (SQLException e) {System.err.println("关闭连接失败: " + e.getMessage());}}connectionPool.clear();usedConnections.clear();System.out.println("数据库连接池已关闭");}public int getAvailableConnectionsCount() {return connectionPool.size();}public int getUsedConnectionsCount() {return usedConnections.size();}
}
3. 客户端使用示例
/*** 单例模式客户端演示*/
public class SingletonPatternDemo {public static void main(String[] args) {System.out.println("=== 单例模式完整演示 ===\n");// 演示1: 各种单例实现方式demonstrateSingletonImplementations();// 演示2: 配置管理器单例使用demonstrateConfigurationManager();// 演示3: 数据库连接池单例使用demonstrateDatabaseConnectionPool();// 演示4: 多线程环境测试demonstrateThreadSafety();// 演示5: 单例破坏与防护demonstrateSingletonProtection();}/*** 演示不同的单例实现方式*/private static void demonstrateSingletonImplementations() {System.out.println("1. 不同单例实现方式演示:");System.out.println("-".repeat(50));// 饿汉式单例EagerSingleton eager1 = EagerSingleton.getInstance();EagerSingleton eager2 = EagerSingleton.getInstance();System.out.println("饿汉式单例是否相同: " + (eager1 == eager2));eager1.showMessage();// 懒汉式单例LazySingleton lazy1 = LazySingleton.getInstance();LazySingleton lazy2 = LazySingleton.getInstance();System.out.println("懒汉式单例是否相同: " + (lazy1 == lazy2));lazy1.showMessage();// 双重检查锁单例DoubleCheckedLockingSingleton dcl1 = DoubleCheckedLockingSingleton.getInstance();DoubleCheckedLockingSingleton dcl2 = DoubleCheckedLockingSingleton.getInstance();System.out.println("双重检查锁单例是否相同: " + (dcl1 == dcl2));dcl1.showMessage();// 静态内部类单例InnerClassSingleton inner1 = InnerClassSingleton.getInstance();InnerClassSingleton inner2 = InnerClassSingleton.getInstance();System.out.println("静态内部类单例是否相同: " + (inner1 == inner2));inner1.showMessage();// 枚举单例EnumSingleton enum1 = EnumSingleton.INSTANCE;EnumSingleton enum2 = EnumSingleton.INSTANCE;System.out.println("枚举单例是否相同: " + (enum1 == enum2));enum1.showMessage();enum1.doBusinessLogic();}/*** 演示配置管理器单例*/private static void demonstrateConfigurationManager() {System.out.println("\n2. 配置管理器单例演示:");System.out.println("-".repeat(50));// 获取配置管理器实例ConfigurationManager configManager = ConfigurationManager.getInstance();// 读取配置System.out.println("应用名称: " + configManager.getConfig("app.name"));System.out.println("数据库URL: " + configManager.getConfig("database.url"));System.out.println("服务器端口: " + configManager.getConfig("server.port"));// 设置新配置configManager.setConfig("cache.enabled", "true");configManager.setConfig("max.connections", "100");// 打印所有配置configManager.printAllConfigs();// 验证单例特性ConfigurationManager anotherInstance = ConfigurationManager.getInstance();System.out.println("配置管理器单例验证: " + (configManager == anotherInstance));}/*** 演示数据库连接池单例*/private static void demonstrateDatabaseConnectionPool() {System.out.println("\n3. 数据库连接池单例演示:");System.out.println("-".repeat(50));DatabaseConnectionPool pool1 = DatabaseConnectionPool.getInstance();DatabaseConnectionPool pool2 = DatabaseConnectionPool.getInstance();System.out.println("连接池单例验证: " + (pool1 == pool2));System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());// 模拟获取和释放连接Connection conn1 = pool1.getConnection();Connection conn2 = pool1.getConnection();System.out.println("获取2个连接后:");System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());pool1.releaseConnection(conn1);pool1.releaseConnection(conn2);System.out.println("释放2个连接后:");System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());}/*** 演示多线程环境下的单例安全性*/private static void demonstrateThreadSafety() {System.out.println("\n4. 多线程安全性演示:");System.out.println("-".repeat(50));int threadCount = 5;Thread[] threads = new Thread[threadCount];for (int i = 0; i < threadCount; i++) {final int threadId = i;threads[i] = new Thread(() -> {InnerClassSingleton singleton = InnerClassSingleton.getInstance();System.out.println("线程 " + threadId + " 获取单例: " + singleton.hashCode());});}for (Thread thread : threads) {thread.start();}for (Thread thread : threads) {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("所有线程获取的单例哈希码相同,证明线程安全");}/*** 演示单例防护机制*/private static void demonstrateSingletonProtection() {System.out.println("\n5. 单例防护机制演示:");System.out.println("-".repeat(50));try {// 尝试通过反射破坏单例// 注意:这里只是演示,实际代码应该避免这样的操作System.out.println("尝试通过反射创建实例...");// 对于枚举单例,反射也无法创建新实例// 对于其他实现,可以在构造函数中添加防护代码} catch (Exception e) {System.out.println("反射破坏被阻止: " + e.getMessage());}}
}
单例模式的优点
1. 严格控制实例数量
// 确保整个应用中只有一个实例
ConfigurationManager config = ConfigurationManager.getInstance();
// 无论在哪里调用,得到的都是同一个实例
2. 全局访问点
// 提供统一的访问入口
DatabaseConnectionPool pool = DatabaseConnectionPool.getInstance();
// 便于管理和维护
3. 延迟初始化
// 静态内部类实现提供了延迟初始化的好处
// 只有在第一次调用getInstance()时才会创建实例
单例模式的缺点
1. 违反单一职责原则
// 单例类同时负责创建实例和管理业务逻辑
public class Singleton {private static Singleton instance;// 业务方法public void businessMethod() { /* ... */ }// 单例管理方法public static Singleton getInstance() { /* ... */ }
}
2. 难以测试
// 由于是全局状态,单元测试时难以mock
public class UserServiceTest {@Testpublic void testUserService() {// 单例的全局状态可能影响测试结果ConfigurationManager config = ConfigurationManager.getInstance();// 测试可能相互影响}
}
适用场景
- 需要频繁实例化然后销毁的对象
- 创建对象时耗时过多或耗资源过多,但又经常用到的对象
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗
最佳实践
1. 优先使用枚举或静态内部类实现
// 推荐方式1:枚举单例
public enum EnumSingleton {INSTANCE;// 业务方法
}// 推荐方式2:静态内部类
public class InnerClassSingleton {private static class Holder {private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();}public static InnerClassSingleton getInstance() {return Holder.INSTANCE;}
}
2. 考虑线程安全
// 确保在多线程环境下也能正常工作
public class ThreadSafeSingleton {private static volatile ThreadSafeSingleton instance;public static ThreadSafeSingleton getInstance() {if (instance == null) {synchronized (ThreadSafeSingleton.class) {if (instance == null) {instance = new ThreadSafeSingleton();}}}return instance;}
}
3. 防止反射和反序列化破坏
public class ProtectedSingleton {private static final ProtectedSingleton INSTANCE = new ProtectedSingleton();private ProtectedSingleton() {// 防止反射攻击if (INSTANCE != null) {throw new RuntimeException("单例已被创建");}}// 防止反序列化破坏private Object readResolve() {return INSTANCE;}
}
总结
单例模式是设计模式中最简单但也最容易误用的模式之一。正确使用单例模式可以带来很多好处,但滥用也会导致很多问题。
核心价值:
- 保证一个类只有一个实例
- 提供全局访问点
- 控制资源使用
使用建议:
- 慎重考虑是否真的需要单例
- 优先使用枚举或静态内部类实现
- 注意线程安全问题
- 考虑测试的便利性
选择指南:
- 如果需要延迟加载:使用静态内部类或双重检查锁
- 如果需要绝对防止反射攻击:使用枚举
- 如果实例化开销小且一定会用到:使用饿汉式
掌握单例模式的正确使用方式,能够在合适的场景下大幅提升代码的质量和性能。