1.懒汉模式
懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。以下是懒汉模式实现方式C++代码:
(1)懒汉模式实现一:静态指针 + 用到时初始化
//代码实例(线程不安全)
template<typename T>
class Singleton
{
public:
static T& getInstance()
{
if (!value_)
{
value = new T();
}
return *value;
}
private:
Singleton();
~Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
static T* value;
};
template<typename T>
T* Singleton<T>::value = nullptr;
在单线程中,这样的写法是可以正确使用的,但是在多线程中就不行了,该方法是线程不安全的。
a. 假如线程A和线程B,这两个线程要访问getInstance函数,线程A进入getInstance函数,并检测if条件,由于是第一次进入,value为空,if条件成立,准备创建对象实例。
b. 但是,线程A有可能被OS的调度器中断而挂起睡眠,而将控制权交给线程B。
c. 线程B同样来到if条件,发现value还是为NULL,因为线程A还没来得及构造它就已经被中断了。此时假设线程B完成了对象的创建,并顺利的返回。
d. 之后线程A被唤醒,继续执行new再次创建对象,这样一来,两个线程就构建两个对象实例,这就破坏了唯一性。
另外,还存在内存泄漏的问题,new出来的东西始终没有释放,下面是一种饿汉式的一种改进。
//代码实例(线程安全)
std::mutex mtx;
template <typename T>
class Singleton
{
public:
static T &getInstance()
{
if (value == nullptr)
{
lock_guard<std::mutex> lck(mtx);
if (value == nullptr)
{
value= new T();
}
}
return *value;
}
private:
Singleton();
~Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
static T* value;
};
template <typename T>
T *Singleton<T>::value_ = nullptr;
(2)懒汉模式实现二:局部静态变量
//代码实例(线程不安全)
template <typename T>
class Singleton
{
public:
static T &getInstance()
{
static T instance;
return instance;
}
private:
Singleton(){};
Singleton(const Singleton &);
Singleton &operator=(const Singleton &);
};
c++98:静态局部变量的实现方式也是线程不安全的。如果存在多个单例对象的析构顺序有依赖时,可能会出现程序崩溃的危险。 对于局部静态对象的也是一样的。因为 static T instance;语句不是一个原子操作,在第一次被调用时会调用Singleton的构造函数,而如果构造函数里如果有多条初始化语句,则初始化动作可以分解为多步操作,就存在多线程竞争的问题。 为什么存在多个单例对象的析构顺序有依赖时,可能会出现程序崩溃的危险?
c++11:线程安全的,是因为,在底层对于static静态局部变量的初始化,编译器会自动加锁和解锁。
2.饿汉模式
单例类定义的时候就进行实例化。因为main函数执行之前,全局作用域的类成员静态变量m_Instance已经初始化,故没有多线程的问题。
class Singleton
{
public:
static Singleton *getinstance()
{
return &instance;
}
private:
static Singleton instance;
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;
优点:
实现简单,多线程安全。
缺点:
a. 如果存在多个单例对象且这几个单例对象相互依赖,可能会出现程序崩溃的危险。原因:对编译器来说,静态成员变量的初始化顺序和析构顺序是一个未定义的行为;具体分析在懒汉模式中也讲到了。
b. 在程序开始时,就创建类的实例,如果Singleton对象产生很昂贵,而本身有很少使用,这种方式单从资源利用效率的角度来讲,比懒汉式单例类稍差些。但从反应时间角度来讲,则比懒汉式单例类稍好些。
使用条件:
a. 当肯定不会有构造和析构依赖关系的情况。
b. 想避免频繁加锁时的性能消耗