Skip to content

单例模式(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)

核心要点

  1. 私有构造函数是单例的基础
  2. 线程安全是懒汉式实现的关键
  3. 枚举和静态内部类是最佳实践
  4. 注意反射和反序列化的破坏
  5. 避免滥用单例导致全局状态泛滥