设计模式手册005:单例模式 - 唯一实例的优雅实现
本文是「设计模式手册」系列第005篇,我们将深入探讨单例模式,这种模式确保一个类只有一个实例,并提供全局访问点,是设计模式中最简单也最容易用错的一种。
1. 场景:我们为何需要单例模式?
在软件开发中,有些对象我们只需要一个实例,比如:
- 配置管理器:整个应用只需要一份配置
- 数据库连接池:避免重复创建连接
- 日志记录器:统一记录日志
- 线程池:管理所有线程资源
- 缓存管理器:全局缓存共享
- 设备驱动对象:物理设备只能有一个访问点
错误做法的代价:
java
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 核心要素
java
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 第一重:饿汉式单例
特点:类加载时就创建实例,线程安全但可能浪费资源
java
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 第二重:懒汉式单例(线程不安全)
特点:延迟加载,但多线程环境下可能创建多个实例
java
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 方法同步
java
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)
java
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 第四重:静态内部类实现
推荐用法:兼顾延迟加载和线程安全
java
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推荐的方式
java
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. 实战案例:完整的配置管理器
java
/**
* 配置管理器单例
* 使用静态内部类实现,兼顾线程安全和延迟加载
*/
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
java
@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的单例注册表
java
@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 线程局部单例
java
/**
* 线程局部单例:每个线程有自己的单例实例
*/
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 单例对象池
java
/**
* 单例对象池:管理有限数量的单例对象
*/
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 反射攻击
java
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 序列化攻击
java
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 单例模式的优点
- 严格控制实例数量:确保全局只有一个实例
- 全局访问点:方便其他对象访问
- 节省资源:避免重复创建相同对象
- 状态共享:所有使用者共享相同状态
9.2 单例模式的缺点
- 违反单一职责原则:同时负责创建和管理实例
- 难以测试:全局状态使得单元测试困难
- 隐藏依赖关系:使用单例的类依赖关系不明确
- 可能成为上帝对象:过度使用导致单例类过于庞大
9.3 设计思考
单例模式的本质是**"受控的全局状态"**。它通过限制实例化来确保状态的一致性,但这也带来了测试和维护的挑战。
深入思考的角度:
"单例模式看似简单,实则暗藏玄机。它需要在简洁性和灵活性之间找到平衡,在确保唯一性的同时,还要考虑线程安全、序列化、反射攻击等各种边界情况。"
在实际应用中,单例模式有很多需要注意的地方:
- Spring的Singleton作用域与经典单例的区别
- 单例在分布式环境下的挑战
- 单例与依赖注入的配合使用
- 如何避免单例成为瓶颈
最佳实践建议:
- 优先使用枚举或静态内部类实现单例
- 考虑使用依赖注入框架管理单例
- 避免在单例中保存可变状态
- 为单例编写适当的销毁方法
- 在多线程环境下充分测试
使用场景判断:
- 适合:线程池、缓存、配置、日志、设备访问
- 不适合:需要测试的业务逻辑、可能变化的状态管理、需要多态的场景
下一篇预告:设计模式手册006 - 建造者模式:如何优雅地构建复杂对象?
版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。