单例模式(Singleton)
前言
单例模式是 23 种设计模式中最简单、最常用的一种。它确保一个类在整个应用生命周期中只有一个实例,并提供一个全局访问点。本文将深入讲解单例模式的核心原理、多种实现方式、线程安全问题及实际应用场景。
一、核心概念
1.1 定义
确保一个类只有一个实例,并提供一个全局访问点。
1.2 核心要素
| 要素 | 说明 |
|---|---|
| 私有构造函数 | 防止外部通过 new 创建实例 |
| 私有静态变量 | 存储唯一实例 |
| 公有静态方法 | 提供全局访问点 |
二、实现方式
2.1 饿汉式(立即初始化)
实例在类加载时就创建,线程安全但无法延迟加载。
php
<?php
class Singleton
{
// 类加载时立即创建实例
private static ?Singleton $instance = null;
// 私有构造函数,防止外部 new
private function __construct() {}
// 全局访问点
public static function getInstance(): Singleton
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function doSomething(): void
{
echo "Singleton doing something" . PHP_EOL;
}
// 防止克隆
private function __clone() {}
// 防止反序列化
public function __wakeup()
{
throw new \Exception("Cannot unserialize singleton");
}
}
// 使用
$instance1 = Singleton::getInstance();
$instance2 = Singleton::getInstance();
var_dump($instance1 === $instance2); // true特点:
- 优点:实现简单,线程安全
- 缺点:类加载时即创建,可能浪费资源
2.2 懒汉式(延迟初始化)
实例在第一次使用时创建,支持延迟加载。
php
<?php
class Singleton
{
private static ?Singleton $instance = null;
private function __construct() {}
public static function getInstance(): Singleton
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}特点:
- 优点:延迟加载,节省资源
- 缺点:非线程安全(多线程下可能创建多个实例)
2.3 双重检查锁(DCL)
在懒汉式基础上增加双重检查,保证线程安全。
php
<?php
class Singleton
{
private static ?Singleton $instance = null;
private static object $lock;
private function __construct() {}
public static function getInstance(): Singleton
{
// 第一次检查:避免不必要的加锁
if (self::$instance === null) {
// PHP 中使用 synchronized 扩展实现
synchronized(self::$lock, function () {
// 第二次检查:防止多线程下重复创建
if (self::$instance === null) {
self::$instance = new self();
}
});
}
return self::$instance;
}
}特点:
- 优点:线程安全,性能较好
- 缺点:实现复杂,部分语言中
volatile关键字必不可少
2.4 静态内部类
利用类加载机制保证线程安全,同时实现延迟加载。
php
<?php
// PHP 没有静态内部类,使用闭包延迟初始化模拟
class Singleton
{
private function __construct() {}
public static function getInstance(): Singleton
{
static $instance = null;
if ($instance === null) {
$instance = new self();
}
return $instance;
}
}特点:
- 优点:线程安全,延迟加载,实现简洁
- 缺点:需要语言支持静态内部类特性
2.5 枚举实现(Java 推荐)
java
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton doing something");
}
}
// 使用
Singleton.INSTANCE.doSomething();特点:
- 优点:最简洁,天然线程安全,防反射攻击,防反序列化
- 缺点:不能延迟加载
三、适用场景
| 场景 | 说明 |
|---|---|
| 配置管理 | 全局配置读取与缓存 |
| 日志记录器 | 统一日志输出入口 |
| 数据库连接池 | 避免重复创建连接 |
| 线程池 | 统一管理线程资源 |
| 缓存系统 | 全局缓存读写 |
| 设备驱动 | 硬件设备唯一访问 |
实际案例:
php
<?php
// 全局配置管理器
class ConfigManager
{
private static ?ConfigManager $instance = null;
private array $config = [];
private function __construct()
{
// 加载配置文件
$this->config = $this->loadConfig();
}
public static function getInstance(): ConfigManager
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function loadConfig(): array
{
return [
'apiUrl' => 'https://api.example.com',
'timeout' => 5000,
'maxRetries' => 3,
];
}
public function get(string $key): mixed
{
return $this->config[$key] ?? null;
}
public function set(string $key, mixed $value): void
{
$this->config[$key] = $value;
}
}
// 使用
$config = ConfigManager::getInstance();
echo $config->get('apiUrl') . PHP_EOL; // https://api.example.com四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 全局唯一实例,减少资源消耗 | 难以进行单元测试 |
| 简化全局状态管理 | 可能隐藏依赖关系 |
| 避免重复创建对象 | 可能导致全局状态问题 |
| 提供统一访问点 | 扩展困难(无接口) |
五、常见踩坑与问题排查
5.1 反射攻击
在 Java 中,反射可以绕过私有构造函数:
java
// 反射破坏单例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance = constructor.newInstance();解决方案:使用枚举实现,或在构造函数中检查实例是否已存在。
php
<?php
// PHP 中通过反射也能绕过私有构造函数
$reflection = new ReflectionClass(Singleton::class);
$constructor = $reflection->getConstructor();
$constructor->setAccessible(true);
$instance = $reflection->newInstanceWithoutConstructor();
// 防御:在构造函数中检查
private function __construct()
{
if (self::$instance !== null) {
throw new RuntimeException('Singleton already exists');
}
}5.2 反序列化问题
序列化/反序列化会创建新实例,破坏单例。
解决方案:实现 __wakeup() 方法:
php
<?php
public function __wakeup()
{
throw new \Exception("Cannot unserialize singleton");
// 或返回现有实例
// return self::getInstance();
}5.3 多线程安全
懒汉式在多线程环境下不安全。
解决方案:使用双重检查锁、静态内部类或枚举实现。
5.4 内存泄漏
长生命周期的单例持有大对象引用可能导致内存泄漏。
解决方案:及时清理不再使用的资源,使用弱引用。
六、优化方案与进阶
6.1 泛型单例
php
<?php
// PHP 无泛型,可通过工厂模式实现类似功能
class SingletonManager
{
private static array $instances = [];
public static function getInstance(string $class): object
{
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}6.2 多例模式(Multiton)
php
<?php
class Database
{
private static array $instances = [];
private function __construct(private string $name) {}
public static function getInstance(string $name): Database
{
if (!isset(self::$instances[$name])) {
self::$instances[$name] = new self($name);
}
return self::$instances[$name];
}
}
$mysql = Database::getInstance('MySQL');
$postgres = Database::getInstance('PostgreSQL');七、全文总结
单例模式的核心是 确保一个类只有一个实例,并提供全局访问点。
实现方式对比:
| 方式 | 线程安全 | 延迟加载 | 复杂度 | 推荐度 |
|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | 低 | ⭐⭐⭐ |
| 懒汉式 | ❌ | ✅ | 低 | ⭐⭐ |
| 双重检查锁 | ✅ | ✅ | 高 | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | 中 | ⭐⭐⭐⭐⭐ |
| 枚举 | ✅ | ❌ | 低 | ⭐⭐⭐⭐⭐(Java) |
核心要点:
- 私有构造函数是单例的基础
- 线程安全是懒汉式实现的关键
- 枚举和静态内部类是最佳实践
- 注意反射和反序列化的破坏
- 避免滥用单例导致全局状态泛滥
