1.回顾单例类
介绍
一个类只能创建一个对象,即单例模式。
该模式可以保证系统中(一个进程)该类只有一个实例,需要提供一个访问它的全局访问点,该实例被所有程序模块共享。[在此进程全局只有唯一一个 且 在任意地方可访问]
应用场景
在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,服务进程中的其他对象通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
饿汉模式:用之前就提前创建好
程序启动时(main函数之前)就创建一个唯一的实例对象
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
using namespace std;
class Singleton
{
public:
static Singleton *GetInstance()
{
return _ponly;
}
void PushData(const string &str)
{
_mtx.lock();
_vec.push_back(str);
_mtx.unlock();
}
void Display()
{
_mtx.lock();
for (auto &e : _vec)
cout << e << endl;
cout << endl;
_mtx.unlock();
}
private:
// 构造函数私有化 -- 禁止类外创建对象
Singleton()
{
}
//_ponly是一个存在于静态区的指针变量
// 这个指针初始化指向 一个Singleton对象
static Singleton *_ponly;
mutex _mtx;
vector<string> _vec;
};
// 在程序入口之前就完成单例对象的初始化 类内声明 类外初始化
Singleton *Singleton::_ponly = new Singleton;
int main()
{
// Singleton s1;err
// static Singleton s2;err
// Singleton* p = new Singleton;err
Singleton::GetInstance()->PushData("彭于晏");
Singleton::GetInstance()->PushData("吴彦祖");
Singleton::GetInstance()->PushData("黎明");
Singleton::GetInstance()->PushData("郭富城");
Singleton::GetInstance()->Display();
return 0;
}
饿汉模式优点:
优点:
- 相对懒汉模式而言简单一些
- 不用关心线程安全问题 饿汉模式在main之前就创建好了对象 而线程是在main函数后进行
饿汉模式缺点:
- 影响进程启动速度
饿汉模式main函数之前就要创建对象
若单例对象初始化很慢(初始化操作很多[读取配置文件])
对象暂时不占用资源 但是会影响后续程序的启动速度 - 多个单例类对象实例启动顺序不确定
两个有依赖关系的单例都是饿汉时 若要求创建顺序:单例1–单例2 饿汉模式无法控制顺序
懒汉模式:用的时候再创建
线程安全问题
static Singleton *GetInstance()
{
// 懒汉模式 不在外部加锁 提高效率 -- 要不然每次创建对象都要加锁
if (_ponly == nullptr)
{
_imtx.lock();
// 线程安全 t1判断为空 new对象 t2来了不为空 不再new 更正了覆盖问问题
if (_ponly == nullptr)
_ponly = new Singleton;
_imtx.unlock();
}
return _ponly;
}
懒汉模式代码
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <thread>
using namespace std;
//g++ test.cc -o test -std=c++17 -lpthread
// 懒汉模式:第一次访问实例对象时[第一次调用GetInstance()]创建
class Singleton
{
public:
// 获取单例对象
static Singleton *GetInstance()
{
if (_instance == nullptr)
{
// std::lock_guard<std::mutex> lock(mutex); 不用再调用unlock
_instanceMtx.lock();
if (_instance == nullptr)
_instance = new Singleton;
_instanceMtx.unlock();
}
return _instance;
}
void PushData(const string &str)
{
_vecmtx.lock();
_vec.push_back(str);
_vecmtx.unlock();
}
void Display()
{
_vecmtx.lock();
for (auto &e : _vec)
cout << e << endl;
cout << endl;
_vecmtx.unlock();
}
~Singleton() {}
// 回收单例对象 _gc是静态局部变量 析构发生在main函数结束后 程序结束时
// 程序结束时 _gc析构 _gc析构函数完成回收单例对象的操作
// 如此实现自动回收单例对象
static void DeleteInstance()
{
_instanceMtx.lock();
if (_instance != nullptr)
{
delete _instance;
_instance = nullptr;
}
_instanceMtx.unlock();
}
class Garbage_Collection
{
public:
~Garbage_Collection()
{
DeleteInstance();
}
};
static Garbage_Collection _gc;
private:
// 有锁时 不禁用拷贝构造也行 因为锁使得vector不能push_back
Singleton() {}
// Singleton(const Singleton &s) = delete;
// Singleton &operator=(const Singleton &s) = delete;
mutex _vecmtx;
vector<string> _vec;
static mutex _instanceMtx;
static Singleton *_instance;
};
mutex Singleton::_instanceMtx;
Singleton *Singleton::_instance = nullptr;
Singleton::Garbage_Collection Singleton::_gc;
/*
第一把锁用于保护单例对象的创建过程
第二把锁用于保护单例对象内部的共享资源
两把锁保证线程安全同时尽可能地减少锁的开销 提高程序的性能
*/
int main()
{
// Singleton s();
srand(time(0));
int n = 5;
thread t1([n]()
{for (size_t i = 0; i < n; ++i)
Singleton::GetInstance()->PushData("线程1: " + to_string(rand())); });
thread t2([n]()
{for (size_t i = 0; i < n; ++i)
Singleton::GetInstance()->PushData("线程2: " + to_string(rand())); });
t1.join();
t2.join();
Singleton::GetInstance()->Display();
return 0;
}
单例对象的回收问题
- 单例对象是动态分配的(用 new 创建),用户需要在程序的某个点(不需要他了,且程序未结束,此时释放它的目的是节约资源)显式释放它,就要提供一个 destroy 函数来手动释放对象。如果全局需要它,就没必要显示释放,进程结束,进程地址空间释放。
- C++11之后所支持的静态局部对象的方式生成懒汉模式中的单例对象,这些都是在栈上 以及 静态区生成的,不需要手动释放。
2.单例配置⽂件类
使⽤配置⽂件加载⼀些程序运⾏的关键信息可以让程序的运⾏更加灵活。
使⽤单例模式管理系统配置信息,能够让配置信息的管理控制更加统⼀灵活。
代码
#ifndef __MY_CONFIG__
#define __MY_CONFIG__
#include <mutex>
#include "fileUtil.hpp"
#include "jsonUtil.hpp"
namespace cloudBackup
{
#define CONFIG_FILE "./cloudBackup.conf" // mysql
class Config
{
private:
// 读取配置文件
bool ReadConfigFile()
{
FileUtil fileUtil(CONFIG_FILE);
std::string content;
// 获取整个文件内容传递给content
if (fileUtil.GetContent(&content) == false)
{
Log::log(Error, "Config::ReadConfigFile::fileUtil.GetContent() failed !: %s: %d", strerror(errno), errno);
return false;
}
// 字符串反序列化成json对象
Json::Value root;
if (JsonUtil::Deserialize(content, &root) == false)
{
Log::log(Error, "Config::ReadConfigFile::JsonUtil::Deserialize() failed !: %s: %d", strerror(errno), errno);
return false;
}
_hotTime = root["hotTime"].asInt();
_serverPort = root["serverPort"].asInt();
_serverIp = root["serverIp"].asString();
_urlPrefix = root["urlPrefix"].asString();
_zipSuffix = root["zipSuffix"].asString();
_zipDir = root["zipDir"].asString();
_backupDir = root["backupDir"].asString();
_backupFileMsg = root["backupFileMsg"].asString();
return true;
}
Config()
{
ReadConfigFile();
}
// 单例类设计
static Config *_instance;
static std::mutex _mutex;
// 配置类主要字段
int _hotTime; // 判定热点文件时间间隔
std::string _serverIp; // 服务器IP地址
int _serverPort; // 服务器监听端口
std::string _urlPrefix; // http请求url前缀 下载/显示/上传
std::string _zipSuffix; // 压缩包后缀
std::string _zipDir; // 压缩包存放路径 非热点文件存放路径
std::string _backupDir; // 云备份文件存放路径 热点文件存放路径
std::string _backupFileMsg; // 已备份文件的属性信息
public:
// 懒汉模式 用的时候再创建实例
static Config *GetInstance()
{
if (_instance == NULL)
{
_mutex.lock();
if (_instance == NULL)
_instance = new Config();
_mutex.unlock();
}
return _instance;
}
int GetHotTime()
{
return _hotTime;
}
int GetServerPort()
{
return _serverPort;
}
std::string GetServerIp()
{
return _serverIp;
}
std::string GetUrlPrefix()
{
return _urlPrefix;
}
std::string GetZipSuffix()
{
return _zipSuffix;
}
std::string GetZipDir()
{
return _zipDir;
}
std::string GetBackupDir()
{
return _backupDir;
}
std::string GetBackupFileMsg()
{
return _backupFileMsg;
}
};
/*类内声明 类外初始化
static Config *_instance;
static std::mutex _mutex;
*/
Config *Config::_instance = NULL;
std::mutex Config::_mutex;
}
#endif //__MY_CONFIG__
配置文件
{
“hotTime” : 30,
“serverPort” : 8080,
“serverIp” : “120.46.25.211”,
“urlPrefix” : “/download/”,
“zipSuffix” : “.lz”,
“zipDir” : “./zipDir/”,
“backupDir” : “./backupDir/”,
“backupFileMsg” : “./backupFile.dat”
}
单例配置类总结
使⽤配置⽂件加载⼀些程序运⾏的关键信息可以让程序的运⾏更加灵活。
使⽤单例模式管理系统配置信息,能够让配置信息的管理控制更加统⼀灵活。
- 提供单例接口 供全局唯一访问
- 读取配置文件 创建backupDir和zipDir