单例模式
使用场景
需要全进程唯一实例时,往往会使用单例模式进行设计
唯一数据入口
假设一个对象负责更改本地设置,例如配置服务的网络端口号。如果出现两个或多个线程,每个线程拥有一个实例,线程并发共同进行配置。每个实例都在修改端口号。最终修改的结果就是不可控的。
共用的资源
现在我们假设有一个数据队列,由三个数据的生产者向队列push数据,一个消费者处理三个不同来源的数据。对队列进行pop操作。这就可能会涉及到四个线程并发的处理。怎样让这四个线程都能够获得到这个队列进行操作?全局变量?nonono不够优雅,这时候,使用单例模式。一个唯一的队列实例。不可以由其他对象创建,整个进程唯一,线程就能够通过这一唯一的实例进行数据的交互。
唯一的控制者
很多时候,我们需要对进程内的资源进行合理的调控,例如服务端进程在某一时刻,突然又大量数据或客户端请求进来。我们需要动态的增加数据处理线程,启用其他闲置的资源等等。这一时刻,决策者只能有一个。如果两个实例分别调控,最终的结果一定不是我们期望的。
实现
单例的实现有两种方式:懒汉、饿汉
懒汉方式
突出一个懒字。意思是只有在使用的时候才去实例化对象。以时间换空间,当访问量较小的时候,可以使用这种方式
为了保证线程安全,一般采用双检锁方法来创建对象
#include <iostream>
#include <pthread.h>
using namespace std;
class CLog
{
public:
static CLog * getInstance();
private:
CLog(){}
CLog(const CLog & o) = delete;
CLog & operator=(const CLog & o) = delete;
private:
static CLog * instance_;
static pthread_mutex_t mutex_;
};
CLog * CLog::instance_ = nullptr;
pthread_mutex_t CLog::mutex_ = PTHREAD_MUTEX_INITIALIZER;
CLog * CLog::getInstance()
{
if (instance_ == nullptr)
{
pthread_mutex_lock(&mutex_);
if (instance_ == nullptr)
{
// instance_ = new CLog(); 可能在执行new的过程中,对象的初始化还没有完成,但是instance_就不是nullptr了,这可能导致另外一个线程使用了未完全初始化的instance_,为避免这种情况,使用下面的方法
CLog * tmp = new CLog();
instance_ = tmp;
}
pthread_mutex_unlock(&mutex_);
}
return instance_;
}
int main()
{
CLog * log = CLog::getInstance();
return 0;
}
饿汉模式
对象还没有使用的时候就已经创建好了。这样以空间换时间
饿汉模式是线程安全的,因为不存在多线程实例化的问题
#include <iostream>
class CLog
{
public:
static CLog * getInstance();
private:
CLog() = default;
~CLog() = default;
CLog(const CLog & o) = delete;
CLog & operator=(const CLog & o) = delete;
private:
static CLog * instance_;
};
CLog * CLog::instance_ = new CLog();
CLog * CLog::getInstance()
{
return instance_;
}
int main()
{
CLog * log = CLog::getInstance();
return 0;
}