单例【Singleton】
实例
饿汉式
注:java中饿单例模式性能 > 懒单例模式,c++中一般使用懒单例模式
优点
加载即创建,线程安全。
缺点
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化
// 饿汉式(java推荐, PHP不支持)
class Singleton
{
/**
* @var Singleton
*/
private static Singleton $instance = new self; // PHP不支持这样写
private function __construct()
{
// Do nothing
}
private function __clone()
{
// Do nothing
}
private function __wakeup()
{
// Do nothing
}
/**
* @return Singleton
*/
public static function getInstance(): Singleton
{
if $instance {
self::$instance = new self;
}
return self::$instance;
}
}
var_dumpgetInstance();
/*
class Singleton#1 (0) {
}
*/
懒汉式
优点
避免了饿汉式的那种在没有用到的情况下创建事例,资源利用率高,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
缺点
懒汉式默认不安全;加锁可保证安全,但会牺牲多线程性能。
4私1公
- 私有构造方法, 防止从类外部实例化
- 私有静态属性, 初始值null, 保存实例
- 私有克隆方法, 防止克隆
- 私有重建方法, 防止重建对象
- 公共的静态方法, 访问这个实例
class Singleton
{
/**
* 私有属性,用于保存当前类实例化后的对象
* @var Singleton|null
*/
private static ?Singleton $instance = null;
// 私有方法,禁止外部程序使用new实例化,只能在内部new
private function __construct()
{
}
// 私有方法,禁止克隆对象
private function __clone()
{
}
// 私有方法,禁止重建对象
private function __wakeup()
{
}
/**
* 公共方法,这是获取当前类对象的唯一方式
* @return Singleton|null
*/
public static function getInstance(): ?Singleton
{
// 多个线程判断instance都为null时,
// 在执行new操作时多线程会出现重复情况
if $instance {
self::$instance = new self;
}
return self::$instance;
}
}
var_dumpgetInstance();
总结
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问以及何时访问它。简单地说就是对唯一实例的受控访问。
意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当您想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
优点
- 节约资源: 系统内存中只有一个对象实例,提高性能,尤其适用于频繁创建/销毁或资源消耗大的对象。
- 受控访问: 对唯一实例提供受控的、唯一的访问方式。
- 伸缩性: 类自身控制实例化过程,方便调整。
- 避免多重占用: 有效避免对共享资源(如文件、连接池)的多重操作导致的问题。
缺点
- 不适用于变化对象: 如果对象状态在不同场景下需要变化,单例会导致数据错误。
- 扩展困难: 缺乏抽象层,难以扩展。
- 潜在问题: 滥用(如连接池溢出)或长时间未用被系统回收(状态丢失)。
- 线程安全: 懒汉式单例在多线程环境下需特别注意,需要互斥锁来防止创建出多个实例。
- 反射破坏: 反射可以直接创建新的实例,破坏单例。
- 不能继承: 大多数单例模式(如饿汉/懒汉)构造器私有,不能被继承。
使用场景
适用于需要频繁使用、创建耗时/耗资源,且对象必须是唯一共享的场合。
- 共享资源管理: 日志文件、应用配置、数据库连接池、线程池。
- 系统级对象: 任务管理器、回收站、文件系统、网站计数器。
- 有状态的工具类。
注意事项
1. 线程安全问题 (并发创建)
- 问题核心: 多个线程可能同时判断实例不存在,导致并发创建出多个实例,违反单例原则(主要发生在懒汉式)。
- 解决方案: 使用互斥锁/同步机制(如
synchronized)锁定实例创建过程。 - 代价: 互斥锁会降低效率/性能。
2. 反射攻击
- 风险: 即使构造方法是私有的,反射机制也能调用私有构造器,从而强制实例化一个新的对象,破坏单例。
- 规避: 在私有构造器内部添加逻辑(如抛出异常),防止通过反射二次创建。
3. 继承限制
- 原因: 经典的懒汉式和饿汉式单例模式都要求构造方法是私有的 (
private)。 - 结果: 私有构造方法导致类不能被继承。
- 例外: 某些模式(如登记式单例)允许被继承。