技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> PHP --> 可序列化单例模式的遗留问题答案

可序列化单例模式的遗留问题答案

浏览:2757次  出处信息

在上一篇文章Serialize/Unserialize破坏单例的最后, 我留下了一个问题, 为了让大家能思考, 我就单独再写一篇给出答案.

上一篇中, 我们说到, 为了实现一个支持序列化的单例模式, 我们采用了如下的定义方式:

  1. class Singleton {
  2.     private static $instance = NULL;
  3.     /** 不容许直接调用构造函数 */
  4.     private function __construct() {
  5.     }
  6.     /** 不容许深度复制 */
  7.     private function __clone() {
  8.     }
  9.     public function __wakeup() {
  10.         self::$instance = $this;
  11.     }
  12.     /** 需要在单利切换的时候做清理工作 */
  13.     public function __destruct() {
  14.         //清理工作
  15.         ....
  16.         self::$instance = NULL;
  17.     }
  18.     public static function getInstance() {
  19.         if (NULL === self::$instance) {
  20.             self::$instance = new self();
  21.         }
  22.         return self::$instance;
  23.     }
  24. }

但这样的看似正确的代码, 确在某些时候达不到我们想要的结果:

  1. $a = Singleton::getInstance();
  2. $a = unserialize(serialize($a));
  3. var_dump($a === Singleton::getInstance());
  4. //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..

现在明白了么?

当然, 最后写成这样:

  1. class Singleton {
  2.     private static $instance = NULL;
  3.     /** 不容许直接调用构造函数 */
  4.     private function __construct() {
  5.     }
  6.     /** 不容许深度复制 */
  7.     private function __clone() {
  8.     }
  9.     public function __wakeup() {
  10.         self::$instance = $this;
  11.     }
  12.     /** 需要在单利切换的时候做清理工作 */
  13.     public function __destruct() {
  14.         //只做清理工作.
  15.     }
  16.     public static function getInstance() {
  17.         if (NULL === self::$instance) {
  18.             self::$instance = new self();
  19.         }
  20.         return self::$instance;
  21.     }
  22. }

建议继续学习:

  1. Serialize/Unserialize破坏单例    (阅读:1862)
  2. Python创建单例模式的三种方式    (阅读:1587)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1