21.4单例模式

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
单例模式(Singleton)结构图 第二十一章 有些类也需计划生育——单例模式 - 图1Singleton类,定义一个 GetInstance 操作,允许客户访问它的唯一实例。GetInstance 是一个静态方法,主要负责创建自己的唯一实例。

  1. class Singleton
  2. {
  3. private static $instance = null;
  4. private function __construct()
  5. {
  6. }
  7. public static function GetInstance()
  8. {
  9. if (self::$instance == null) {
  10. self::$instance = new Singleton();
  11. }
  12. return self::$instance;
  13. }
  14. }

客户端代码

  1. public function singletonDemo()
  2. {
  3. $s1 = SingletonDemo::GetInstance();
  4. $s2 = SingletonDemo::GetInstance();
  5. if ($s1 === $s2) {
  6. echo '两个对象是相同的实例';
  7. }
  8. }

单例模式除了可以保证唯一的实例外的其他好处:
比如单例模式因为 Singleton 类封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。

21.5多线程时的单例(拓展)

多线程的程序中,多个线程同时,注意是同时访问 Singleton 类,调用 GetInstance() 方法,会有可能造成创建多个实例的。
可以给进程一把锁来处理。这里需要解释一下 lock 语句的涵义,lock 是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

  1. class Singleton
  2. {
  3. private static Singleton instance;
  4. private static readonly object syncRoot = new Object();
  5. private Singleton()
  6. {
  7. }
  8. public static Singleton GetInstance()
  9. {
  10. lock (syncRoot)
  11. {
  12. if (instance == null)
  13. {
  14. instance = new Singleton();
  15. }
  16. }
  17. return instance;
  18. }
  19. }

这段代码使得对象实例由最先进入的那个线程创建,以后的线程在进入时不会再去创建对象实例了。由于有了lock,就保证了多线程环境下同时访问也不会造成多个实例的生成。
不直接lock(instance), 而是再创建一个syncRoot来lock,是因为加锁时,instance实例不知道有没有创建过实例。

21.6双重锁定(拓展)

  1. class Singleton
  2. {
  3. private static Singleton instance;
  4. private static readonly object syncRoot = new Object();
  5. private Singleton()
  6. {
  7. }
  8. public static Singleton GetInstance()
  9. {
  10. //先判断实例是否存在,不存在再加锁处理
  11. if (instance == null)
  12. {
  13. lock (syncRoot)
  14. {
  15. if (instance == null)
  16. {
  17. instance = new Singleton();
  18. }
  19. }
  20. }
  21. return instance;
  22. }
  23. }

现在这样,我们不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。这种做法被称为 Double-Check Locking (双重锁定)。

21.7静态初始化(拓展)

C#与公共语言运行库也提供了一种‘静态初始化’方法,这种方法不需要开发人员显示地编写线程安全代码,即可解决多线程环境下它是不安全的问题。

  1. //sealed 阻止发生派生,而派生可能会增加实例
  2. public sealed class Singleton
  3. {
  4. //在第一次引用类的任何成员时创建实例。
  5. //公共语言运行库负责处理变量初始化
  6. private static readonly Singleton instance = new Singleton();
  7. private Singleton() { }
  8. public static Singleton GetInstance()
  9. {
  10. return instance;
  11. }
  12. }

这样的实现与前面的示例类似,也是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性为访问实例提供了一个全局访问点。不同之处在于它依赖公共语言运行库来初始化变量。由于构造方法是私有的。因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。不过要注意,instance 变量标记为 readonly,这意味着只能在静态初始化期间或在类构造函数中分配变量。
由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例类。
懒汉式单例类与饿汉式单例类的区别:
由于饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以到底使用哪一种方式,取决于实际的需求。从C#语言角度来讲,饿汉式的单例类已经足够满足我们的需求了。