提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
单例模式几乎是每种语言都不可少的一种设计模式,今天就针对C++语言来解读下集中单例模式,并给出代码说明。
一、单例是什么?
单例就是单实例,简而言之内存中只存在这一个实例
,在很多场景下很有用,比如线程池
、内存池
等等。
二、解读
按照创建时机可以分为饿汉式
和懒汉式
。所谓饿汉式就是程序启动的时候立即创建,饿汉式就是需要的时候创建。
1.懒汉式
懒汉式顾名思义,不会主动创建,直到主动获取才创建。懒汉式又主要分为两种,线程安全
和线程不安全
。接下来分别介绍这几种和提供源代码示例。
简单模式:
cpp
class Singleton {
static Singleton *getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton *instance;
};
Singleton *Singleton::instance = nullptr;
优点:
性能和饿汉式差不多,没有锁,性能不会损失
缺点:
线程不安全
总结:
需要自己确保线程安全,比如启动线程前就要创建好,避免竞争条件产生
线程安全:
cpp
#include <mutex>
class Singleton {
public:
static Singleton *getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton *instance;
static std::mutex mtx;
};
Singleton *Singleton::instance = nullptr;
std::mutex Singleton::mtx;
优点:
线程安全,随时随地都是安全的
缺点:
性能差,每次获取实例都要上锁
总结:
不建议使用
双重检查锁:
cpp
#include <mutex>
class Singleton {
public:
static Singleton *getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
private:
static Singleton *instance;
static std::mutex mtx;
};
Singleton *Singleton::instance = nullptr;
std::mutex Singleton::mtx;
优点:
线程安全,性能比上面的强
缺点:
性能有轻微损耗,只有第一次创建才需要上锁,后面访问不需要上锁
总结:
强烈推荐,既解决了线程安全问题,又解决了性能问题
2.饿汉式
不会产生数据竞争,程序启动时就创建和,在竞争条件出现之前创建完成,所以先天是线程安全的。
cpp
class Singleton {
public:
static Singleton &getInstance() {
return instance;
}
private:
static Singleton instance;
};
Singleton Singleton::instance;
优点:
线程安全,性能最好
缺点:
无论需不需要都会创建,会浪费资源
总结:
可用但不太推荐
3.static变量特性
C++11之后static修饰的局部变量
有一个很重要的特性:初始化的时候会自动加锁,这个特性是编译器负责维护的。所以可以借用这个特性开发一个单例模式,一定是线程安全
的,而且提供不俗的性能
。
注:这个单例又叫米尔斯单例(Meyers' Singleton)
cpp
class Singleton {
public:
static Singleton &getInstance() {
static Singleton instance;
return instance;
}
private:
Singleton() {}
public:
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
};
优点:
线程安全,性能不俗
缺点:
初始化上锁略微拖慢性能,后面正常获取,C++11以后版本才行
总结:
极其推荐
4.call_once特性
除了米尔斯单例,还有一个C++11的新特性可以设计单例,这个特性我们平常容易忽视,那就是call_once。它也是一种线程同步机制,C++保证call_once保护的方法只被调用一次
。
cpp
#include <atomic>
class Singleton {
public:
static Singleton& getInstance(){
std::call_once(flag,[]{
instance = new Singleton();
});
return *instance;
}
private:
static Singleton* instance;
static std::once_flag flag;
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;
优点:
线程安全,性能不俗,针对方法可以同时初始化多个资源
缺点:
第一次调用拖慢性能,后面就不会了,C++11以后版本才行
总结:
和米尔斯很像,但是实现比米尔斯复杂,推荐指数放在米尔斯单例之后
总结
1、C++11以上版本优先使用米尔斯单例,比双重检查锁性能强
2、其他场景建议双重检查锁,call_once酌情使用