php单例模式核心是确保类唯一实例并提供全局访问点,需私有构造、静态实例、延迟初始化及防克隆/反序列化;php非多线程环境无需同步,但swoole等常驻框架需协程/进程隔离;单例易致隐藏依赖与测试困难,推荐优先使用di容器管理。

PHP 单例模式的核心是:确保一个类只有一个实例,并提供全局访问点。面试中常考的不是“会不会写”,而是“是否理解为什么这么写”——比如私有构造、静态实例、延迟初始化、防止克隆和反序列化破坏等细节。
基础单例结构(含关键注释)
这是最常被要求手写的版本,重点在控制实例创建和访问方式:
- 构造方法私有化:阻止外部 new 实例
- 静态属性保存唯一实例:用 self::$instance 而非 Static::$instance,避免继承时子类共享父类实例(除非刻意设计)
- getInstance() 必须是静态且公有:作为唯一入口,首次调用时创建,后续直接返回已有实例
class Database { private static $instance = null; private function __construct() { // 初始化连接等操作可放这里 } public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } // 防止克隆 private function __clone() {} // 防止反序列化生成新实例 private function __wakeup() {} }
线程安全?PHP 中其实不需考虑
PHP 是共享内存模型(每个请求独占一个进程/线程),不存在多线程并发竞争同一内存空间的问题。所以不需要像 Java 那样加 synchronized 或双重检查锁(double-Checked Locking)。所谓“线程安全单例”在 PHP-FPM 或 CLI 场景下是伪需求。
但要注意:若用 Swoole 或 workerman 这类常驻内存框架,多个协程/进程可能共用一个对象,此时需结合协程 ID 或进程 ID 做隔离,或改用依赖注入容器管理实例生命周期。
立即学习“PHP免费学习笔记(深入)”;
常见破坏单例的操作及防御
面试官常故意问:“怎么防止别人绕过 getInstance() 得到新实例?” 答案就是封死所有可能入口:
- __clone():禁止 clone $db1 得到 $db2
- __wakeup():防止 unserialize() 触发新构造
- __sleep():可选,返回空数组避免序列化敏感数据
- 若类可被继承,且希望子类也遵守单例,getInstance() 应用 static:: 而非 self::;若不允许继承,加 final class
单例真的必要吗?面试加分项
能指出单例的副作用,说明你有工程判断力:
- 隐藏依赖,降低可测试性(mock 困难)
- 全局状态易导致耦合,尤其单元测试中状态污染
- 多数场景用依赖注入(DI)+ 容器作用域(如 singleton scope)更灵活、更易维护
可以说:“我倾向优先用 DI 容器管理单例生命周期,只在极简脚本或遗留系统中手写单例。”