21.4单例模式
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
单例模式(Singleton)结构图
Singleton类,定义一个 GetInstance 操作,允许客户访问它的唯一实例。GetInstance 是一个静态方法,主要负责创建自己的唯一实例。
class Singleton
{
private static $instance = null;
private function __construct()
{
}
public static function GetInstance()
{
if (self::$instance == null) {
self::$instance = new Singleton();
}
return self::$instance;
}
}
客户端代码
public function singletonDemo()
{
$s1 = SingletonDemo::GetInstance();
$s2 = SingletonDemo::GetInstance();
if ($s1 === $s2) {
echo '两个对象是相同的实例';
}
}
单例模式除了可以保证唯一的实例外的其他好处:
比如单例模式因为 Singleton 类封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
21.5多线程时的单例(拓展)
多线程的程序中,多个线程同时,注意是同时访问 Singleton 类,调用 GetInstance() 方法,会有可能造成创建多个实例的。
可以给进程一把锁来处理。这里需要解释一下 lock 语句的涵义,lock 是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new Object();
private Singleton()
{
}
public static Singleton GetInstance()
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
}
这段代码使得对象实例由最先进入的那个线程创建,以后的线程在进入时不会再去创建对象实例了。由于有了lock,就保证了多线程环境下同时访问也不会造成多个实例的生成。
不直接lock(instance), 而是再创建一个syncRoot来lock,是因为加锁时,instance实例不知道有没有创建过实例。
21.6双重锁定(拓展)
class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new Object();
private Singleton()
{
}
public static Singleton GetInstance()
{
//先判断实例是否存在,不存在再加锁处理
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
现在这样,我们不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。这种做法被称为 Double-Check Locking (双重锁定)。
21.7静态初始化(拓展)
C#与公共语言运行库也提供了一种‘静态初始化’方法,这种方法不需要开发人员显示地编写线程安全代码,即可解决多线程环境下它是不安全的问题。
//sealed 阻止发生派生,而派生可能会增加实例
public sealed class Singleton
{
//在第一次引用类的任何成员时创建实例。
//公共语言运行库负责处理变量初始化
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
这样的实现与前面的示例类似,也是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性为访问实例提供了一个全局访问点。不同之处在于它依赖公共语言运行库来初始化变量。由于构造方法是私有的。因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。不过要注意,instance 变量标记为 readonly,这意味着只能在静态初始化期间或在类构造函数中分配变量。
由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例类。
懒汉式单例类与饿汉式单例类的区别:
由于饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以到底使用哪一种方式,取决于实际的需求。从C#语言角度来讲,饿汉式的单例类已经足够满足我们的需求了。