总结单例类在实际应用中存在的问题与解决办法
单例多线程中存在的问题
经过前面的学习,已知单例会在成员函数中判断 Instancce是否为nullptr,但是但在多线程中由于操作系统时间片的调度问题,可能会在线程1中判断完 if(Instance == nullptr),并确定该判断语句成立之后,切换到线程2,线程2也会执行 if(Instance == nullptr)语句,并且可能会执行Instance = new obj(),之后再切换回线程1继续执行后续语句,那么就会导致线程1,线程2都创建了obj类的对象。
双重锁
一般会想到在创建单例中加锁,但是如果每一次调用函数时都经历加锁和解锁的过程,就会严重影响程序执行的效率,那么将其进行优化。
lock_guard简化了 lock/unlock 的写法, lock_guard在构造时自动锁定互斥量, 而在退出作用域时会析构自动解锁。
注意重点 m_instance!=nullptr,那么m_instance一定被new了,但是if(m_instance==nullptr)成立,不一定m_instance没被new过,因为上文所说的操作系统时间片的调度问题。使用两层 if()判断语句,在二者之间加锁,那就确保只会在一个线程中进行new,因为第一个if判断不能肯定是否被new过,但是成功锁住之后再次判断,未被满足if判断就会new一个,不满足那就是被new过了。
static mutex mtx; //锁
static obj* get_Instance()
{
if(m_instance == nullptr)
{
lock_guard<mutex> lock(mtx);
if(m_instance == nullptr)
{
m_instance = new obj();
}
}
return m_instance;
}
同时还要注意“内存访问重新排序”所导致的双重锁失效的问题,因此该方法并不太推荐。
饿汉式
就如名字一样,饿汉式很“饿”,因此在给Instance赋初值的时候不再赋值为nullptr,而是直接new obj类对象。
class obj
{
private:
obj(){}
public:
static obj* get_Instance()
{
return m_instance;
}
private:
static obj* m_instance; //指向本类对象的指针
};
//直接定义赋值
obj* obj::m_instance = new obj();
用 new 创建的 obj对象的代码会早于 main() 主函数执行,所以在main()中obj::m_instance已经有了有效值,后续对get_Instance调用就不需要加锁。
懒汉式
不同于饿汉式一开始就创建,懒汉式就是在需要的时候再进行创建。程序执行之后该单例对象并不存在,只有第一次调用get_Instance成员函数该单例类对象才会创建,这种方式可以更好的控制单例的创建时机,以免过早加载,如果obj类非常庞大的话很容易造成资源消耗。
class obj
{
private:
obj(){}
public:
static obj* get_Instance()
{
if(m_instance == nullptr)
{
m_instance = new obj();
}
return m_instance;
}
private:
static obj* m_instance; //指向本类对象的指针
};
//主函数中使用
obj* p1 = boj::get_Instance(); //或者直接使用boj::get_Instance()
一个好的解决多线程创建obj类单例对象的方法就是在main()主函数中,在创建任何其他线程之前先执行一次“boj::get_Instance();”将单例对象单独创建出来,这样后续调用getInstance函数的时候,就只读取到m_instance成员变量,那么也就不需要加锁了。
学习资料:《C++新经典设计模式》 王建伟编著