举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。
继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1.主要优点
2.主要缺点
3、通常适用的场景
懒汉式的方法,顾名思义,很懒的,配置文件的实例直到用到的时候才会加载。
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
#include
// version1:
// with problems below:
// 1. thread is not safe
// 2. memory leak
class Singleton{
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
Singleton(Singleton&)=delete;//=delete禁止使用编译器默认生成的函数
Singleton& operator=(const Singleton&)=delete;
static Singleton* m_instance_ptr;
public:
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
static Singleton* get_instance(){
if(m_instance_ptr==nullptr){
m_instance_ptr = new Singleton;
}
return m_instance_ptr;
}
void use() const { std::cout << "in use" << std::endl; }
};
Singleton* Singleton::m_instance_ptr = nullptr;
int main(){
Singleton* instance = Singleton::get_instance();
Singleton* instance_2 = Singleton::get_instance();
return 0;
}
可以看到,获取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这是个最基础版本的单例实现,他有哪些问题呢?
加锁
使用共享指针
;1、非C++11的加锁实现方式:pthread_mutex_t
class single{
private:
//私有静态指针变量指向唯一实例
static single *p;
//静态锁,是由于静态函数只能访问静态成员
static pthread_mutex_t lock;
//私有化构造函数
single(){
pthread_mutex_init(&lock, NULL);
}
~single(){}
public:
//公有静态方法获取实例
static single* getinstance();
};
pthread_mutex_t single::lock;
single* single::p = NULL;
single* single::getinstance(){
if (NULL == p){
pthread_mutex_lock(&lock);
if (NULL == p){
p = new single;
}
pthread_mutex_unlock(&lock);
}
return p;
}
2、使用C++11智能指针和互斥锁的方式
#include
#include // shared_ptr
#include // mutex
// version 2:
// with problems below fixed:
// 1. thread is safe now
// 2. memory doesn't leak
class Singleton{
public:
typedef std::shared_ptr<Singleton> Ptr;
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
Singleton(Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
static Ptr get_instance(){
//注意这里双重判断
if(m_instance_ptr==nullptr){
std::lock_guard<std::mutex> lk(m_mutex);//C++11加互斥锁,可以自动解锁
if(m_instance_ptr == nullptr){
m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
}
}
return m_instance_ptr;
}
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
static Ptr m_instance_ptr;
static std::mutex m_mutex;
};
// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
std::mutex Singleton::m_mutex;
int main(){
Singleton::Ptr instance = Singleton::get_instance();
Singleton::Ptr instance2 = Singleton::get_instance();
return 0;
}
shared_ptr和mutex都是C++11的标准,以上这种方法的优点是
shared_ptr
, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr
析构的时候,new
出来的对象也会被 delete
掉。以此避免内存泄漏。为什么需要 “double checked lock”?
对于 Instance
存在的情况,就直接返回,这没有问题。当 Instance为null
并且同时有两个线程调用 GetInstance()
方法时,它们将都可以通过第一重 Instance=null
的判断。然后由于lock
机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有了第二重的 Instance
是否为null
的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。
#include
class Singleton
{
public:
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
Singleton(const Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
static Singleton& get_instance(){
static Singleton instance;
return instance;
}
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
};
int main(int argc, char *argv[])
{
Singleton& instance_1 = Singleton::get_instance();
Singleton& instance_2 = Singleton::get_instance();
return 0;
}
1、通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
2、不需要使用共享指针,代码简洁;
3、注意在使用的时候需要声明单例的引用 Single&
才能获取对象。
这时候有人说了,这种方法不加锁会不会造成线程安全问题?
其实,C++0X以后,要求编译器保证内部静态变量的线程安全性,故C++0x之后该实现是线程安全的,C++0x之前仍需加锁,其中C++0x是C++11标准成为正式标准之前的草案临时名字。
一旦加载就创建一个单例,保证在调用 getInstance
方法之前单例已经存在了。如果说懒汉式是“时间换空间”
,那么饿汉式就是“空间换时间”
,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
//全局的单例实例在类装载时构建。
#include
using namespace std;
class Singleton
{
private:
static Singleton* pInstance;
Singleton()
{
cout << "constructor called!" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator = (const Singleton&) = delete;
public:
static Singleton* getInstance();
~Singleton() {
std::cout << "destructor called!" << std::endl;
}
void print()
{
cout << "this = " << this << endl;
}
};
Singleton* Singleton::pInstance = new Singleton;全局的单例实例在类装载时构建。
// 静态方法返回该实例
Singleton* Singleton::getInstance()
{
return pInstance;
}
int main()
{
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->print();
s2->print();
return 0;
}
优点:
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
缺点:
当类Singleton
被加载的时候,会初始化static的instance
,静态变量被创建并分配内存空间,从这以后,这个static的instance
对象便一直占着这段内存
(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
1、https://www.cnblogs.com/xiaoxi/p/7799456.html
2、http://c.biancheng.net/view/1338.html
3、https://www.cnblogs.com/sunchaothu/p/10389842.html