单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例的一般实现比较简单,代码如下:
class Singleton
{
public:
static Singleton* GetInstance(); //此方法时获得本类实例的唯一全局访问点
private:
Singleton() {} //构造函数让其private,这样就可以堵死外界利用new创建此实例的可能
static Singleton *singleton;
};
Singleton* Singleton::singleton = NULL;
Singleton* Singleton::GetInstance()
{
if(singleton == NULL)
singleton = new Singleton();
return singleton;
}
这种单例模式,在多线程的程序中,多个线程同时,注意是同时访问Singleton类,调用GetInstance()方法时,会有可能造成创建多个实例的。
单例模式分为懒汉模式,跟饿汉模式两种 。
懒汉模式
懒汉模式下,在定义singleton变量时先等于NULL,在调用GetInstance()方法时,在判断是否要赋值。单例的一般实现其实就是一种懒汉模式,不过在多线程中不安全,懒汉模式在多线程中安全。
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {}
static Singleton *singleton;
};
Singleton* Singleton::GetInstance()
{
if (singleton == NULL)
{
Lock();
if (singleton == NULL)
{
singleton = new Singleton();
}
UnLock();
}
return singleton;
}
Singleton* Singleton::singleton = NULL;
问题1:第一个判断singleton是否为NULL这个是否可以去掉?
答:如果去掉,这样每次调用GetInstance()方法时都需要lock,这样会影响性能。
问题2:为什么要双重singleton==NULL的判断?
答:对于singleton存在的情况,就直接返回,这没有问题。当singleton为NULL并且同时有两个线程调用GetInstance()方法时,它们将都可以通过第一重singleton==NULL的判断。然后由于lock机制,这两个线程则只有一个进入,另一个在外排队等待,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有了第二重的singleton==NULL的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。
饿汉模式
饿汉模式:即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。
由静态初始化实例保证其线程安全性,WHY?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。
故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {}
static Singleton *singleton;
};
Singleton* Singleton::GetInstance()
{
return singleton;
}
Singleton* Singleton::singleton = new Singleton();
特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。