文章目录
-
- 什么是单例模式?
- 核心思想
- 模式结构
- 单例模式的多种实现方式
-
- [1. 饿汉式单例(Eager Initialization)](#1. 饿汉式单例(Eager Initialization))
- [2. 懒汉式单例(Lazy Initialization)](#2. 懒汉式单例(Lazy Initialization))
- [3. 双重检查锁单例(Double-Checked Locking)](#3. 双重检查锁单例(Double-Checked Locking))
- [4. 静态内部类单例(Holder Pattern)](#4. 静态内部类单例(Holder Pattern))
- [5. 枚举单例(Enum Singleton)](#5. 枚举单例(Enum Singleton))
- 完整示例:配置管理器单例
-
- [1. 配置管理器单例类](#1. 配置管理器单例类)
- [2. 数据库连接池单例](#2. 数据库连接池单例)
- [3. 客户端使用示例](#3. 客户端使用示例)
- 单例模式的优点
-
- [1. 严格控制实例数量](#1. 严格控制实例数量)
- [2. 全局访问点](#2. 全局访问点)
- [3. 延迟初始化](#3. 延迟初始化)
- 单例模式的缺点
-
- [1. 违反单一职责原则](#1. 违反单一职责原则)
- [2. 难以测试](#2. 难以测试)
- 适用场景
- 最佳实践
-
- [1. 优先使用枚举或静态内部类实现](#1. 优先使用枚举或静态内部类实现)
- [2. 考虑线程安全](#2. 考虑线程安全)
- [3. 防止反射和反序列化破坏](#3. 防止反射和反序列化破坏)
- 总结
什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式是设计模式中最简单但也是最容易误用的模式之一。
核心思想
单例模式的核心思想是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 它通过控制对象的创建过程,确保在整个应用程序中只存在一个实例。
模式结构
单例模式包含一个核心角色:
- 单例类(Singleton):负责创建自己的唯一实例,并提供访问该实例的全局方法
单例模式的多种实现方式
1. 饿汉式单例(Eager Initialization)
java
/**
* 饿汉式单例 - 线程安全
* 在类加载时就完成实例化,避免了多线程同步问题
*/
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)
java
/**
* 懒汉式单例 - 基础版本(线程不安全)
* 在第一次调用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)
java
/**
* 双重检查锁单例 - 高性能线程安全版本
* 既保证了线程安全,又提高了性能
*/
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)
java
/**
* 静态内部类单例 - 推荐使用的实现方式
* 结合了饿汉式的线程安全和懒汉式的延迟加载
*/
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)
java
/**
* 枚举单例 - 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. 配置管理器单例类
java
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. 数据库连接池单例
java
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. 客户端使用示例
java
/**
* 单例模式客户端演示
*/
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. 严格控制实例数量
java
// 确保整个应用中只有一个实例
ConfigurationManager config = ConfigurationManager.getInstance();
// 无论在哪里调用,得到的都是同一个实例
2. 全局访问点
java
// 提供统一的访问入口
DatabaseConnectionPool pool = DatabaseConnectionPool.getInstance();
// 便于管理和维护
3. 延迟初始化
java
// 静态内部类实现提供了延迟初始化的好处
// 只有在第一次调用getInstance()时才会创建实例
单例模式的缺点
1. 违反单一职责原则
java
// 单例类同时负责创建实例和管理业务逻辑
public class Singleton {
private static Singleton instance;
// 业务方法
public void businessMethod() { /* ... */ }
// 单例管理方法
public static Singleton getInstance() { /* ... */ }
}
2. 难以测试
java
// 由于是全局状态,单元测试时难以mock
public class UserServiceTest {
@Test
public void testUserService() {
// 单例的全局状态可能影响测试结果
ConfigurationManager config = ConfigurationManager.getInstance();
// 测试可能相互影响
}
}
适用场景
- 需要频繁实例化然后销毁的对象
- 创建对象时耗时过多或耗资源过多,但又经常用到的对象
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗
最佳实践
1. 优先使用枚举或静态内部类实现
java
// 推荐方式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. 考虑线程安全
java
// 确保在多线程环境下也能正常工作
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. 防止反射和反序列化破坏
java
public class ProtectedSingleton {
private static final ProtectedSingleton INSTANCE = new ProtectedSingleton();
private ProtectedSingleton() {
// 防止反射攻击
if (INSTANCE != null) {
throw new RuntimeException("单例已被创建");
}
}
// 防止反序列化破坏
private Object readResolve() {
return INSTANCE;
}
}
总结
单例模式是设计模式中最简单但也最容易误用的模式之一。正确使用单例模式可以带来很多好处,但滥用也会导致很多问题。
核心价值:
- 保证一个类只有一个实例
- 提供全局访问点
- 控制资源使用
使用建议:
- 慎重考虑是否真的需要单例
- 优先使用枚举或静态内部类实现
- 注意线程安全问题
- 考虑测试的便利性
选择指南:
- 如果需要延迟加载:使用静态内部类或双重检查锁
- 如果需要绝对防止反射攻击:使用枚举
- 如果实例化开销小且一定会用到:使用饿汉式
掌握单例模式的正确使用方式,能够在合适的场景下大幅提升代码的质量和性能。