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


·        作者Laruence(   )

·        本文地址http://www.laruence.com/2011/03/18/1916.html

·        转载请注明出处

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

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

1.       class Singleton {

2.           private static$instance = NULL;

3.        

4.           /** 不容许直接调用构造函数 */

5.           private function__construct() {

6.        

7.           }

8.        

9.           /** 不容许深度复制 */

10.        private function__clone() {

11.        }

12.     

13.        public  function__wakeup() {

14.            self::$instance = $this;

15.        }

16.     

17.        /** 需要在单利切换的时候做清理工作 */

18.        public function__destruct() {

19.            //清理工作

20.            ....

21.            self::$instance = NULL;

22.        }

23.     

24.        public staticfunctiongetInstance() {

25.            if (NULL === self::$instance) {

26.                self::$instance = newself();

27.            }

28.     

29.            return self::$instance;

30.        }

31.    }

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

1.       $a = Singleton::getInstance();

2.       $a = unserialize(serialize($a));

3.        

4.       var_dump($a === Singleton::getInstance());

5.       //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.           /** 不容许直接调用构造函数 */

5.           private function__construct() {

6.        

7.           }

8.        

9.           /** 不容许深度复制 */

10.        private function__clone() {

11.        }

12.     

13.        public  function__wakeup() {

14.            self::$instance = $this;

15.        }

16.     

17.        /** 需要在单利切换的时候做清理工作 */

18.        public function__destruct() {

19.            //只做清理工作.

20.        }

21.     

22.        public staticfunctiongetInstance() {

23.            if (NULL === self::$instance) {

24.                self::$instance = newself();

25.            }

26.     

27.            return self::$instance;

28.        }

29.    }

 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值