动机
在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。
实现方式
饿汉模式
饿汉模式就是在类加载的时候立刻进行实例化
// 饿汉模式
class TaskQueue {
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
int main() {
TaskQueue* obj = TaskQueue::getInstance();
}
懒汉模式
饿汉模式就是在类被调用的时候进行实例化
一般写法
// 懒汉模式
class TaskQueue {
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
if(m_taskQ == nullptr) {
m_taskQ = new TaskQueue;
}
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
线程安全问题
解法1:使用互斥锁,但是每次调用锁,导致程序性能下降
class TaskQueue {
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
m_mutex.lock();
if (m_taskQ == nullptr) {
m_taskQ = new TaskQueue;
}
m_mutex.unlock();
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;
解法2:双重检查锁定
class TaskQueue {
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
if (m_taskQ == nullptr) {
m_mutex.lock();
if (m_taskQ == nullptr) {
m_taskQ = new TaskQueue;
}
m_mutex.unlock();
}
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;
上面这种通过两个嵌套的 if 来判断单例对象是否为空的操作就叫做双重检查锁定。但是在多线程环境下,new有可能被打断,导致m_taskQ 指针是不为空的,但这个指针指向的内存却没有被初始化。C++11 中引入了原子变量 atomic:
class TaskQueue {
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
TaskQueue* queue = m_taskQ.load();
if (queue == nullptr) {
lock_guard<mutex> locker(m_mutex);
queue = m_taskQ.load();
if (queue == nullptr) {
queue = new TaskQueue;
m_taskQ.store(queue);
}
}
return queue;
}
private:
TaskQueue() = default;
static atomic<TaskQueue*> m_taskQ;
static mutex m_mutex;
};
atomic<TaskQueue*> TaskQueue::m_taskQ;
mutex TaskQueue::m_mutex;
但是这种方法实现的懒汉模式的单例执行效率更低一些。
静态局部对象
在 C++11 标准中有如下规定:
如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。
class TaskQueue{
public:
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance() {
static TaskQueue taskQ;
return &taskQ;
}
private:
TaskQueue() = default;
};
总结
懒汉模式:
- 缺点:线程不安全,但是C++11的局部静态对象,可以完美解决
- 优点:减少内存浪费
饿汉模式:
- 缺点:浪费内存
- 优点:线程安全