浅析设计模式——单例模式
1、单例类的好处之一在于唯一化实例、从而可以实现对唯一实例的受控访问
该唯一性是通过将构造函数私有来实现的,既然构造函数被设置为private,那么外部函数将不能调用构造函数来创建实例(比如在外部函数中编写Singleton* singletonptr=new Singleton();将会报错)。
那么问题来了,单例类虽然只能创建一个实例,但终归是要能创建嘛。为了实现创建这个唯一的实例,需要在单例类中定义一个public的静态成员函数GetInstance(),外部函数通过这个公有的接口来获取这个唯一的实例。(之所以用到static,是因为静态成员函数同一般函数的区别在于没有this指针,对于静态函数而言,没有this指针也就意味着即使没有对象也能调用它(单例类常会用到静态成员函数和静态数据成员)。外部函数既然都不能直接实例化对象,又要调用该单例类的public成员函数,那么就需要将该成员函数设置为静态成员函数。)
在公有的静态成员函数GetInstance()中,需要先判断是否已经实例化,如果已经实例化则直接返回该实例,如果没有才会创建一个实例;
Singleton* Singleton::GetInstance() {
if(instance==NULL) {
instance=new Singleton();
}
return instance;
}
对于下列代码中有一行很重要,不要遗漏:
Singleton* Singleton::instance = new Singleton();
这一行是为了实例化这个唯一的对象,这是饿汉式,如果直接赋值为NULL则是懒汉式
2、单例类虽然实例单一、但是可以有子类来继承;
3、单例类还分为恶汉式单例类和懒汉式单例类,此外多线程时还需要有其他的考虑(比如加锁)。
考虑所有情况以后的单例模式代码,Linux下编译通过:
#include<iostream>
#include<pthread.h>
using namespace std;
pthread_mutex_t mutex;
class Singleton{
public:
static Singleton* GetInstance(); //获取唯一实例接口
private:
Singleton(); //私有的构造函数
static Singleton* instance; //唯一的实例
};
Singleton::Singleton() {
}
//Singleton* Singleton::instance = new Singleton(); //饿汉式,空间换时间懒汉式和饿汉式对线程安全的不同影响
Singleton* Singleton::instance = NULL; //懒汉式,时间换空间
Singleton* Singleton::GetInstance() {
if (instance == NULL) { //二次判断!!!
pthread_mutex_lock(&mutex); //static int* volatile instance=0/new Singleton();
if (instance == NULL){ //用volatile关键字告诉编译器此处的变量instance很有可能被其他地方改变,不能进行优化
instance = new Singleton(); //还有此处的CPU乱序问题,导致指针指向还没有被构造出来的内存
}
pthread_mutex_unlock(&mutex);
}
return instance;
}
int main(){
pthread_mutex_init(&mutex, NULL);
Singleton* singleton1 = Singleton::GetInstance();
Singleton* singleton2 = Singleton::GetInstance();
if (singleton1 == singleton2) //判断这两个实例是否是同一个
cout << "we are the same instance" << endl;
pthread_mutex_destroy(&mutex);
return 0;
}
4、CPU乱序问题:
1>用volatile关键字告诉编译器此处的变量instance很有可能被其他地方改变,不能进行优化
static int* volatile instance=0/new Singleton();
2>instance=newSingleton();实质上由三个步骤组成:1分配内存、2在内存的位置上调用构造函数、3将内存地址赋值给指针instance。由于CPU的乱序执行,很有可能将步骤2和3颠倒,此时会出现一种情况就是;还未调用构造函数就把内存地址赋值给了指针,假如有另一个线程调用getInstance函数,那么由于Instance指针非空,该线程就会获取到这个非空但并没有构造完全的对象指针,如果使用该指针的话,会带来很恶劣的影响。
解决方法:使用barrier指令阻止CPU将该指令之前的指令交换到该指令之后