目录
单例模式:
1)定义
简单定义:保证这个类在程序里面只有一个实例存在。
单例模式的基本结构需满足以下要求。
- 单例模式的核心结构只有一个单例类,单例模式要保证这个类在运行期间只能被实例化一次,即只会被创建唯一的一个单例类的实例。
- 单例模式需要提供一个全局唯一能得到这个类实例的访问点,一般通过定义一个名称类似为GetInstance的公用方法实现这一目的。
单例模式的基本结构图:
2)实现方法:
实现一个单例模式需要:1>私有的构造函数。2>一个静态方法,返回这个唯一实例的引用。3>一个指针静态变量。4>选择一个解决多线程问题的方法。
1.把构造函数声明为私有,则只有Singleton类内的可以调用构造函数。
2.用静态方法来实例化这个对象,并返回这个对象。
3.利用一个静态变量来记录Singleton类的唯一实例。
4.解决多线程问题的方法见下面代码
单例模式又分为饿汉式单例和懒汉式单例。
饿汉式单例在单例类被加载时就实例化一个对象交给自己的引用;
而懒汉式在调用取得实例方法的时候才会实例化对象。
在C++ 中一般都使用懒汉式单例,但懒汉式单例可能会有线程安全问题
3)优点和应用场景:
单例模式的优点:
1. 在内存中只有一个对象,节省内存空间
2. 避免频繁的创建销毁对象,可以提高性能
3. 避免对共享资源的多重占用
4. 可以全局访问
单例模式的适用场景:
1. 需要频繁实例化然后销毁的对象
2. 创建对象耗时过多或者耗资源过多,但又经常用到的对象
3. 有状态的工具类对象
4. 频繁访问数据库或文件的对象
5. 以及其他要求只有一个对象的场景
单例模式适用于各种系统中某个类的对象只能存在一个这样的场景
(比如进程池,linux高性能服务器p290)
c++实现懒汉模式:
1)muduo C++网络库的线程安全单例模式:
//pthread_once api pthread_once_t 必须是全局变量or静态变量
// 保证所有线程中 init_routine 只被调用一次
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
//muduo c++ 网络库的单例模式
//singleton.h
template<typename T>
class Singleton : boost::noncopyable
{
public:
static T& instance()
{
pthread_once(&ponce_, &Singleton::init);
return *value_;
}
private:
Singleton();
~Singleton();
static void init()
{
value_ = new T();
}
private:
static pthread_once_t ponce_;
static T* value_;
};
//注意:必须头文件中定义static变量 (模板类的静态变量是这样的,但是普通类的静态成员初始化在源文件中)
template <typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;
//在C++中,std::call_once和std::once_flag可以代替上面的pthread_once和pthread_once_t。(直接换就行)
使用方法:
MyClass& mc = Singleton<MyClass>::instance();
2)单线程版:
class Singleton{
private:
Singleton(); // 私有默认构造函数
Singleton(const Singleton& other); // 私有拷贝构造函数
static Singleton* m_instance;
public:
static Singleton* getInstance();
};
Singleton* Singleton::m_instance=nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
3)各种多线程版:
a) 上锁(线程安全但效率不高)
class Singleton{
private:
Singleton(); // 私有默认构造函数
Singleton(const Singleton& other); // 私有拷贝构造函数
static Singleton* m_instance;
static pthread_mutex_t mutex; // 互斥锁
public:
static Singleton* getInstance();
};
Singleton *Singleton::m_instance=nullptr;
pthread_mutex_t Singleton::mutex= PTHREAD_MUTEX_INITIALIZER; // 静态的互斥锁,初始方法
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
pthread_mutex_lock(&mutex);
if (m_instance == nullptr) {
m_instance = new Singleton();
}
pthread_mutex_unlock(&mutex);
return m_instance;
}
b)双检查锁(DCL)(会导致未初始化的内存访问)
由于指令乱序执行,可能导致线程A先进入,在执行m_instance = new Singleton()的时候,先分配内存,再赋值给m_instance,最后执行Singleton的构造函数。但是在赋值之后,未初始化前,另一线程B可能通过getInstance获取到这个指针,导致线程B拿到的是一个指向未初始化的对象。
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){
pthread_mutex_lock(&mutex);
if (m_instance == nullptr) {
m_instance = new Singleton();
}
pthread_mutex_unlock(&mutex);
}
return m_instance;
}
c)使用内存栅栏(线程安全)
//C++ 11版本之后的跨平台实现 (volatile) 内存栅栏
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
饿汉模式:
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。(本身就是线程安全的,如下例子)
关于如何选择懒汉和饿汉模式:
特点与选择:
懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。
饿汉:由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
class Singleton{
private:
Singleton(); // 私有默认构造函数
Singleton(const Singleton& other); // 私有拷贝构造函数
static Singleton* m_instance;
public:
static Singleton* getInstance();
};
// 饿汉模式的关键:初始化即实例化
Singleton* Singleton::m_instance=new Singleton();
//饿汉模式本身就是线程安全的
Singleton* Singleton::getInstance() {
return m_instance;
}