单例模式是设计模式中一种常用模式,定义是Ensure a class has only one instance, and provide a global point of access to it.(确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例)
用《设计模式之禅》里的话说,就是,在一个系统中,要求一个类有且仅有一个对象,如果出现多个就会出现“不良反应”,比如以下情景可能会用到单例模式
说了这么多,但是模式如何设计?
为了避免后面的派生类继承这个类,误将该类多次初始化(即内存中多个备份),我们应该将其构造函数设为私有,并且把唯一的一次初始化留到静态函数中,比如c++写出来是这样的
#include <iostream>
template<typename T>
class Singleton
{
public:
static T& instance()
{
if (flag_init)
{
flag_init = false;
flag_destory = true;
init();
}
return *value_;
}
static void init()
{
value_ = new T();
}
static void destory()
{
if (flag_destory)
{
flag_destory = false;
suicide();
}
}
static void suicide()
{
delete value_;
}
private:
static T* value_; //只进行一次的初始化的指针
static bool flag_init; //表明是否可以进行初始化的标志位
static bool flag_destory; //表明是否进行过销毁的标志位
Singleton(){} //私有函数,导致无法多次初始化这个类
~Singleton(){} //私有函数,导致无法多次销毁这个类
};
//静态变量必须在外面初始化
template<typename T>
T* Singleton<T>::value_ = NULL;
template<typename T>
bool Singleton<T>::flag_destory = false;
template<typename T>
bool Singleton<T>::flag_init = true;
//测试单例是否可用的测试类
class Example
{
public:
Example(){value = 0;}
~Example(){}
void tool()
{
value++;
std::cout << value<< std::endl;
}
private:
int value;
};
int main(int argc, char *argv[])
{
Example& ex1 = Singleton<Example>::instance();
ex1.tool();
Example& ex2 = Singleton<Example>::instance();
ex2.tool();
Singleton<Example>::destory();
Singleton<Example>::destory();
return 0;
}但是这样就行了吗,如果在多线程会怎么样, 如果是多线程的话,在同时修改创建或者销毁的两个bool值时就可能发生错误。
那么怎么解决呢,有人提出了DCLP(double checked locking pattern)机制,结合互斥锁mutex,询问两次的方法。
分析:
当多个线程同时进入instance()时,都会发现第一个if (value_ == NULL)为真,之后开始竞争,拿到锁的线程会直接通过第二个if (value_ == NULL)进行初始化,然后释放锁,其他的线程拿到之后会到达第二个if ,此时实例已经被初始化,直接返回实例,不会进行二次初始化
实现如下
<span style="font-size:12px;">#include <iostream>
#include <unistd.h>
#include "Mutex.h"
template<typename T>
class Singleton
{
public:
static T& instance()
{
if (value_ == NULL)
{
MutexLockGuard guard(mutex_); //这个表示区域锁,实现后附代码,原理参见《Linux多线程服务端编程》陈硕著
if (value_ == NULL)
{
init();
}
}
return *value_;
}
static void init()
{
value_ = new T();
}
private:
static T* value_;
static MutexLock mutex_;
Singleton(){}
~Singleton(){}
};
//静态变量必须在外面初始化
template<typename T>
T* Singleton<T>::value_ = NULL;
template<typename T>
MutexLock Singleton<T>::mutex_;
//测试单例是否可用的测试类
class Example : boost::noncopyable
{
public:
Example(){value = 0;}
~Example(){}
void tool()
{
value++;
std::cout << value<< " ";
}
private:
int value;
};
void* thread(void*arg)
{
Example& ex3 = Singleton<Example>::instance();
ex3.tool();
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
pthread_create(&tid, NULL, thread, NULL);
Example& ex1 = Singleton<Example>::instance();
ex1.tool();
sleep(1);
return 0;
}</span>编译时记得加参数。。。-pthread运行检测,好像多线程也没什么问题,但是这样真的可以吗,国外大神过来打脸了
meyers指出 new Singleton,这步(也就是init()函数里的new)在真正运行时会分解成三个行为
分配内存
构造实例对象
将指针指向分配的内存
这三个指令可能会被CPU重排,然后执行顺序发生变化比如 3->1->2
这在一般情况下不会有异常,因为乱序执行就是cpu的一种优化手段(详情自行查阅,内容很多,不展开叙述),而且在外层有互斥锁的保护。但是,我们的互斥锁的保护是有条件的,只有先经过第一个if 判断才能进入互斥锁保护范围,而这个条件却被3所影响,倘若cpu先执行了3,这时另一个cpu同时进行 1处的判断,发现指针已不为空,直接返回对象供上层使用,而这时你返回的却是一个根本还没构造完毕的对象!
pthread_once()由Pthreads库保证,某个函数在多线程下只执行一次,实现出来是这样的:
#include <iostream>
#include <boost/noncopyable.hpp>
template<typename T>
class Singleton
{
public:
static T& instance()
{
pthread_once(&ponce_, &Singleton::init);
return *value_;
}
static void init()
{
value_ = new T();
}
private:
static pthread_once_t ponce_;
static T* value_;
};
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;
<span style="font-size:12px;">int
__pthread_once (once_control, init_routine)
pthread_once_t *once_control;
void (*init_routine) (void);
{
/* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
global lock variable or one which is part of the pthread_once_t
object. */
if (*once_control == PTHREAD_ONCE_INIT)
{
lll_lock (once_lock, LLL_PRIVATE);
/* XXX This implementation is not complete. It doesn't take
cancelation and fork into account. */
if (*once_control == PTHREAD_ONCE_INIT)
{
init_routine ();
*once_control = !PTHREAD_ONCE_INIT;
}
lll_unlock (once_lock, LLL_PRIVATE);
}
return 0;
}</span>看了以下,发现这个实现其实就是DCLP机制,不信你看
if (*once_control == PTHREAD_ONCE_INIT)出现了两次,原理上和之前我们自己写的差不多,那它为什么可靠呢,其实关键在于
<span style="font-size:12px;"> lll_lock (once_lock, LLL_PRIVATE);</span>
经过查询和查大神博客,这个是一个基于gcc内嵌指令的宏,不同硬件平台实现不一样,我们只记作用就好,作用是
所以就避免了CPU指令乱序重排,有人对c++比较熟,会说那我给之前的变量加上volatile关键字就好了,对,但是linux下的c++,这里特指c++98标准的volatile很鸡肋,没有做到内存屏障的作用也就无法实现控制指令顺序的作用,所以自己很难实现,直接用pthread_once就好,如果是新的版本可以尝试最初的DCLP做法,这里有详细博文,我就不赘述了
前面代码中出现了继承boost::noncopyable,这个是一个常用做法,noncopyable类把构造函数和析构函数设置成protected权限,这样子类可以调用,外面的类不能调用,说白了就是外面的调用者不能够通过赋值和copy构造新子类
#include <boost/noncopyable>
class Example: public boost::noncopyable
{
public:
Example(){};
Example(int i){};
};
int main()
{
Example cl1();
Example cl2(1);
//Example cl3(cl1); // error
//Example cl4(cl2); // error
return 0;
} 最后奉上皓叔的单例模式讲解,这个是以java为例的
本文在阅读书籍和查阅网上资料完成,如有不足之处,请提出
参考资料:
《Linux多线程服务端编程》陈硕 著
《设计模式之禅》秦小波 著
原文:http://blog.csdn.net/cgxrit/article/details/43741771