- 全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式即为单例模式(典型应用:任务队列)
饿汉模式
- 饿汉模式即在类加载时立刻进行实例化,是线程安全的!
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
// 提供给外部的接口,用于访问唯一实例
static TaskQueue* getInstance()
{
return m_taskQ;
}
// 提供销毁实例的方法
static void releaseInstance()
{
delete m_taskQ;
m_taskQ = nullptr;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
int main()
{
TaskQueue* obj = TaskQueue::getInstance();
TaskQueue::releaseInstance();
return 0;
}
懒汉模式
- 懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化,并非线程安全的。
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
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;
}
// 提供销毁实例的方法
static void releaseInstance()
{
delete m_taskQ;
m_taskQ = nullptr;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = nullptr;
注:单线程情况下调用getInstance()函数获取单例对象是没有问题的,但若是多线程情况,多个线程同时执行getInstance()函数,这时每个线程都会new出一个实例对象,与单例模式的定义相悖,故懒汉模式存在线程安全问题!
二者的区别
- 懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。
利用静态局部变量(C++11)的懒汉模式
- C++11保证静态局部变量的初始化是线程安全!
#include <iostream>
class Singleton {
private:
Singleton() {} // 私有构造函数
public:
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance; // 静态局部变量,线程安全 (C++11 保证)
return instance;
}
void doSomething() {
std::cout << "Static Local Variable Singleton instance" << std::endl;
}
};
int main() {
Singleton& singleton = Singleton::getInstance();
singleton.doSomething();
return 0;
}
结合智能指针的懒汉模式
使用智能指针来管理静态局部对象,真正的
Singleton对象是动态分配在堆上,unique_ptr负责管理它的生命周期。 而使用静态局部对象Singleton对象存放在静态存储区。 使用上两者差不多,但智能指针版本可以自定义删除其、统计分配信息等。
#include <iostream>
#include <memory>
class Singleton {
private:
// 私有构造函数,防止外部实例化
Singleton() = default;
public:
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static std::unique_ptr<Singleton> instance(new Singleton());
return *instance;
}
void doSomething() {
std::cout << "Static Local Variable Singleton instance" << std::endl;
}
};
int main() {
Singleton& singleton = Singleton::getInstance();
singleton.doSomething();
return 0;
}
基于饿汉模式的任务队列
// 基于饿汉模式的线程安全的任务队列
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
class TaskQueue
{
public:
// 提供给外部的接口,用于访问唯一实例
static TaskQueue* getInstance()
{
return &m_obj;
}
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
// 任务队列是否为空
bool isEmpty()
{
std::lock_guard<std::mutex> locker(m_mutex);
bool flag = m_taskQ.empty();
return flag;
}
// 添加任务
void addTask(int data)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_taskQ.push(data);
}
// 取出任务
int takeTask()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
return m_taskQ.front();
}
return -1;
}
// 删除任务
bool popTask()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
m_taskQ.pop();
return true;
}
return false;
}
private:
TaskQueue() = default;
static TaskQueue m_obj;
std::queue<int> m_taskQ;
std::mutex m_mutex; // 互斥锁,用以确保queue操作的线程安全
};
// 静态成员初始化放到类外部处理
TaskQueue TaskQueue::m_obj;
int main()
{
std::thread t1([](){
TaskQueue* taskQ = TaskQueue::getInstance();
for (int i = 0; i < 100; ++i)
{
taskQ->addTask(i + 100);
std::cout << "+++push task: " << i + 100 << ", threadID: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
});
std::thread t2([](){
TaskQueue* taskQ = TaskQueue::getInstance();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (!taskQ->isEmpty())
{
int data = taskQ->takeTask();
std::cout << "---task task: " << data << ", threadID: " << std::this_thread::get_id() << std::endl;
taskQ->popTask();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
t1.join();
t2.join();
return 0;
}