- 本文地址: https://www.laruence.com/2011/03/18/1916.html
- 转载请注明出处
在上一篇文章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; } }
但这样的看似正确的代码, 确在某些时候达不到我们想要的结果:
$a = Singleton::getInstance(); $a = unserialize(serialize($a)); var_dump($a === Singleton::getInstance()); //bool(false)
那么为什么呢?
我之前的文章深入理解PHP原理之变量分离/引用(Variables Separation)中曾经介绍过, 在PHP中, 采用引用计数的方式来减少对内存的使用和提高效率.
回头来看这个问题, 根据运算符的结合律, 我们来单步分析这个过程:
在我们调用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; } }
[…] 大家可以想想为什么这样,, 如果不想想的, 就看我的下一篇文章吧. […]
你好,我想了解一下,php 会不会像其他语言(C# JAVA),出现线程同步的问题?
是否要加锁?
[…] 风雪之隅 » PHP源码分析 Posted in: php / Tagged: 可序列化单例模式的遗留问题答案 […]
good
用工厂来模型进行单例处理,用类自身的方法太麻烦了,通用性也不好。
补充一下,serialize时也不调用__destruct()的,和unserialize不调用__construct()保持了一致;同时serialize的时候也没有释放原有对象;
$a = Singleton::getInstance();
$b = Singleton::getInstance();
//这是$a和$b是同一个对象的引用,单例
$b = serialize($b);
//$a 保持不变,$b为字符串
$b = unserialize($b);
//另外一个对象了
结论:
1、$a和$b依然是两个object;
2、__destruct()中的self::$instance = NULL;无作用;
3、__warkup()中的self::$_instance = $this;改变了Singleton::$instance的指向,原本是指向$a的,现在指向了$b;
4、unserialize是不调用__construct()的
问题:
不知道怎么实现支持serialize的单例?
[…] http://www.laruence.com/2011/03/18/1916.html 可序列化单例模式的遗留问题答案 […]
[…] 本文地址: http://www.laruence.com/2011/03/18/1916.html […]
这样写还是有bug的。
$ex = Singleton::getInstance();
$now = unserialize(serialize($ex));
var_dump($ex === $now);
//bool(false)
理想的方案是是当unserialize返回已有的对象,
public function __wakeup() {
return self::$instance;
}
可惜不支持return。。。
恩,受教了,博主研究很深入啊
[…] //bool(false) 大家可以想想为什么这样,, 如果不想想的, 就看我的下一篇文章吧. 最后, 做个广告,,,, 在新浪微博关注我吧: http://t.sina.com.cn/laruence, Related […]