读过鸟哥的博客,一直以为常规定义单例的方式就很正确,结果看完博客后发现序列化能够破坏单例。
原文地址:laruence http://www.laruence.com
常规定义单例的方式:
class Singleton {
private static $instance = NULL;
/** 不容许直接调用构造函数 */
private function __construct() {
}
/** 不容许深度复制 */
private function __clone() {
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
Serialize/Unserialize破坏单例
$a = Singleton::getInstance();
$b = unserialize(serialize($a));
var_dump($a === $b); //bool(false)
修改1 (阻止Serialize/Unserialize)
class Singleton {
private static $instance = NULL;
/** 不容许直接调用构造函数 */
private function __construct() {
}
/** 不容许深度复制 */
private function __clone() {
}
/** 不容许serialize */
private function __sleep() {
}
/** 不容许unserialize */
private function __wakeup() {
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
修改2 (允许Serialize/Unserialize,并保证单例)
class Singleton {
private static $instance = NULL;
/** 不容许直接调用构造函数 */
private function __construct() {
}
/** 不容许深度复制 */
private function __clone() {
}
public function __wakeup() {
self::$instance = $this;
}
/** 需要在单例切换的时候做清理工作 */
public function __destruct() {
self::$instance = NULL;
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
修改2 存在问题
$a = Singleton::getInstance();
$a = unserialize(serialize($a));
var_dump($a === Singleton::getInstance());
//bool(false)
修改2 原因
在我们调用unserialize(serialize($a))
的时候, 在serialize之前, PHP会首先尝试调用我们的类的实例$a的__sleep方法, 因为我们没有定义此方法, 所以跳过此步骤..
接下来, 在unserialize的时候, PHP在完成对象的创建以后, 会来调用新创建对象的__wakeup方法 , 在这里面, 我们释放了原有的self::$instance的引用, 改变成了新的对象.
这个时候, 原来的$a
, 并不会被释放, 因为此时符号名a还保留着对$a
(单例类的一个实例)的引用, 但此时$a所指的对象的引用计数已经-1, 变成了1, (应该还要了解到, 此时, 还会对Object Store中的对象引用计数-1, 也变为了1)
最后, 我们把得到的新对象给$a
赋值, OK, 关键的时候来了, 这个时候, 因为我们重新对$a
赋值, 所以$a
会释放之前所值向的zval的引用, 造成了此时这个zval的引用计数变为了零, 于是PHP就会释放这个zval, 也就会调用了Singleton的析构函数, 在这个析构函数中, 我们释放了静态实例$instance..
最终修改方案
class Singleton {
private static $instance = NULL;
/** 不容许直接调用构造函数 */
private function __construct() {
}
/** 不容许深度复制 */
private function __clone() {
}
public function __wakeup() {
self::$instance = $this;
}
/** 需要在单例切换的时候做清理工作 */
public function __destruct() {
//只做清理工作
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。