1、写一个简单的单例模型(单例类)
解释:下面代码由于静态成员在程序启动时就已经创建好,不需要实例化所以用类名::静态函数就可以使用,使用一个类指针指向该静态成员函数来获取类对象。
重要三大步:
- 定义一个静态的GetInstance方法,返回该类的唯一实例
- 定义一个自身的静态成员变量,只有当该类未初始化时创建该类的实例。
- 将类的构造函数定义为私有的,不允许外界访问构造函数,创建实例。
#include <iostream>
#include <windows.h>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstence()
{
if (m_singleton == NULL)
m_singleton = new Singleton();
return m_singleton;
}
private:
Singleton(){}
static Singleton* m_singleton;
};
Singleton* Singleton::m_singleton = NULL;
int main()
{
Singleton* s1 = Singleton::GetInstence();
Singleton* s2 = Singleton::GetInstence();
if (s1 == s2)
cout << "this is a class" << endl;
system("pause");
return 0;
}
存在的问题
虽然将该类的构造函数定义为私有,但是拷贝构造函数和赋值运算符重载函数也可以进行创建类对象,所以我们还需要将拷贝构造和赋值运算符重载函数设置为类的私有成员。
int main()
{
Singleton* s1 = Singleton::GetInstence();
Singleton* s2 = Singleton::GetInstence();
if (s1 == s2)
cout << "this is a class" << endl;
Singleton s3(*s1);
if(s1 != &s3)
cout<<"this is no class"<<endl;
system("pause");
return 0;
}
2、对上一个单例的改进(将拷贝构造和赋值运算符重载设置为私有)
满足上面的三个条件还需要将两个构造函数设置为私有
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
if (nullptr == m_singleton)
m_singleton = new Singleton();
return m_singleton;
}
private:
Singleton(){}
Singleton(const Singleton& s);
Singleton& operator=(const Singleton& s);
static Singleton* m_singleton;
};
Singleton* Singleton::m_singleton = nullptr;
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
//Singleton s3(s1);
//Singleton s4 = s1;
return 0;
}
存在的问题,上面解决了单例的问题,但是还存在一些线程安全的问题。
static Singleton* GetInstance()
{
//thread1
//thread2
if (nullptr == m_singleton)
m_singleton = new Singleton();
return m_singleton;
}
解释:当上面两个线程同时执行到GetInstance()时,由于m_singleton = new Singleton();
这一行代码是由多条指令组成,比如这行代码分成是三个部分,先开辟空间(多条指令),
调构造函数(多条指令),赋值(多条指令),这里只能保证执行一句指令是原子的。thread1
当代码执行多条指令后,但是没有执行到赋值的指令,就是m_singleton还是nullptr的时候,
这个线程的时间片完成了,就要指向先一个线程thread2,thread2认为m_singleton为空,
所以就继续new一个对象,将原来的对象覆盖,这样相当于创建了多个实例对象,危险的是
第一个new出来的对象没有销毁,造成内存泄漏的问题。
所以接下来解决线程安全问题
3、解决线程安全问题
我们可以使用对需要保护的代码加锁
当一个线程进入互斥锁保护区时,其他线程就不能进入,只能等待这个线程执行完毕
VS2013
#include <iostream>
#include <windows.h>
#include <mutex> //互斥锁头文件C++
#include <thread>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
_mtx.lock();
if (m_singleton == NULL)
{
m_singleton = new Singleton();
}
_mtx.unlock();
return m_singleton;
}
private:
Singleton(){};
Singleton(const Singleton& s);
Singleton& operator=(const Singleton& s);
static Singleton* m_singleton;
static mutex _mtx;
};
mutex Singleton::_mtx;
Singleton* Singleton::m_singleton = nullptr;
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
system("pause");
return 0;
}
Linux下的加锁自行学习
上面解决了线程安全的问题,但是代码不是高效的,因为只有第一次因为上面的代码只需要线程第一次来的时候加锁,之后代码被改变m_singleton不为空了,也就不需要加锁了,多线程支持多读单写的,一个线程可以改变m_singleton,但是不影响其他线程的读if (m_singleton == NULL)的操作,加锁后会影响效率的问题,所以我们使用双重检查(提高效率)
4、双重检测是代码高效
#include <iostream>
#include <windows.h>
#include <mutex>
#include <thread>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
//外层判断解决效率,内层解决线程安全
if (m_singleton == nullptr)
{
_mtx.lock();
if (m_singleton == nullptr)
{
m_singleton = new Singleton();
}
_mtx.unlock();
}
return m_singleton;
}
private:
Singleton(){};
Singleton(const Singleton& s);
Singleton& operator=(const Singleton& s);
static Singleton* m_singleton;
static mutex _mtx;
};
mutex Singleton::_mtx;
Singleton* Singleton::m_singleton = nullptr;
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
system("pause");
return 0;
}
问题:上面还是存在一些问题的,new和构造函数都可能会跑异常,这样的话m_singleton还是NULL,只要来调用GetInstance的线程全部阻塞在这里,会造成死锁,因为第一线程被有解锁,程序就冻结起来了,
5、解决死锁
1、这个情况就需要捕获异常进行解锁,
2、或者自己定义一个类,构造函数里面加锁,析构函数解锁
3、使用RAII方式,C++11中的lock_guard lock(_mtx);
#include <iostream>
#include <windows.h>
#include <mutex>
#include <thread>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
lock_guard<mutex> lock(_mtx);
if (m_singleton == nullptr)
{
m_singleton = new Singleton();
}
return m_singleton;
}
private:
Singleton(){};
Singleton(const Singleton& s);
Singleton& operator=(const Singleton& s);
static Singleton* m_singleton;
static mutex _mtx;
};
mutex Singleton::_mtx;
Singleton* Singleton::m_singleton = nullptr;
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
system("pause");
return 0;
}
6、单例分为懒汉模式和饿汉模式,上面的就是懒汉模式,只有等到使用对象的时候才开始创建对象,效率比较低。饿汉模式是提前创建好对象,需要的时候直接使用
#include <iostream>
#include <cassert>
#include <windows.h>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
assert(m_singleton);
return m_singleton;
}
private:
static Singleton* m_singleton;
};
Singleton* Singleton::m_singleton = new Singleton();
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
system("pause");
return 0;
}
还有另外一种方法
#include <iostream>
#include <windows.h>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton* m_singleton = new Singleton();
return m_singleton;
}
private:
Singleton(){}
static Singleton* m_singleton;
};
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
if (s1 == s2)
cout << "s1 == s2" << endl;
system("pause");
return 0;
}