对象性能_Singleton
目录
对象性能
面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。 但是某些情况,面向对象所带来的成本必须谨慎处理。
典型模式
- Singleton
- Flyweight
1 singleton 单例模式
1.1 模式动机
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例? 这应该是类设计者的责任,而不是使用者的责任。
1.2 模式定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
1.3 模式示例代码
线程不安全
# include <iostream>
# include <string>
// Singleton类:日志记录器
class Logger {
public:
// 公共静态方法,用于获取Logger实例
static Logger* getInstance() {
if (instance == nullptr) {
instance = new Logger();
}
return instance;
}
void log(const std::string& message) {
std::cout << "日志记录:" << message << std::endl;
}
private:
// 私有构造函数,防止外部实例化对象
Logger() {}
// 静态成员变量,存储唯一实例
static Logger* instance;
};
// 初始化静态成员变量
Logger* Logger::instance = nullptr;
int main() {
// 获取Logger实例
Logger* logger = Logger::getInstance();
// 记录日志
logger->log("这是一条日志信息");
// 尝试实例化Logger对象(无效,因为构造函数是私有的)
// Logger* logger2 = new Logger();
// 释放内存(可选)
delete logger;
return 0;
}
线程安全
# include <iostream>
# include <string>
# include <mutex>
// Singleton类:日志记录器
class Logger {
public:
// 公共静态方法,用于获取Logger实例
static Logger* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex); // 加锁
if (instance == nullptr) {
instance = new Logger();
}
}
return instance;
}
void log(const std::string& message) {
std::cout << "日志记录:" << message << std::endl;
}
private:
// 私有构造函数,防止外部实例化对象
Logger() {}
// 静态成员变量,存储唯一实例
static Logger* instance;
// 静态互斥量,用于线程安全
static std::mutex mutex;
};
// 初始化静态成员变量
Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;
int main() {
// 获取Logger实例
Logger* logger = Logger::getInstance();
// 记录日志
logger->log("这是一条日志信息");
// 释放内存(可选)
delete logger;
return 0;
}
双检查锁 (double-checked locking)
# include <iostream>
# include <string>
# include <mutex>
// Singleton类:日志记录器
class Logger {
public:
// 公共静态方法,用于获取Logger实例
static Logger* getInstance() {
// 锁前检查
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex); // 加锁
// 锁后检查
if (instance == nullptr) {
// Todo <2023-07-06, @xcx> : 可能会有 reorder 的问题, order 不确定
// 1. 分配内存
// 2. 调用构造函数
// 3. 赋值给 instance
// or
// 1. 分配内存
// 2. 赋值给 instance
// 3. 调用构造函数
instance = new Logger();
}
}
return instance;
}
void log(const std::string& message) {
std::cout << "日志记录:" << message << std::endl;
}
private:
// 私有构造函数,防止外部实例化对象
Logger() {}
// 静态成员变量,存储唯一实例
static Logger* instance;
// 静态互斥量,用于线程安全
static std::mutex mutex;
};
// 初始化静态成员变量
Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;
int main() {
// 获取Logger实例
Logger* logger = Logger::getInstance();
// 记录日志
logger->log("这是一条日志信息");
// 释放内存(可选)
delete logger;
return 0;
}
解决 reorder 问题
# include <iostream>
# include <string>
# include <atomic>
# include <mutex>
// Singleton类:日志记录器
class Logger {
public:
// 公共静态方法,用于获取Logger实例
static Logger* getInstance() {
Logger* tmp = instance.load(std::memory_order_acquire); // 读取实例
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex); // 加锁
tmp = instance.load(std::memory_order_relaxed); // 重新读取实例
if (tmp == nullptr) {
tmp = new Logger();
instance.store(tmp, std::memory_order_release); // 存储实例
}
}
return tmp;
}
void log(const std::string& message) {
std::cout << "日志记录:" << message << std::endl;
}
private:
// 私有构造函数,防止外部实例化对象
Logger() {}
// 静态原子指针,存储唯一实例
static std::atomic<Logger*> instance;
// 静态互斥量,用于线程安全
static std::mutex mutex;
};
// 初始化静态原子指针
std::atomic<Logger*> Logger::instance(nullptr);
std::mutex Logger::mutex;
int main() {
// 获取Logger实例
Logger* logger = Logger::getInstance();
// 记录日志
logger->log("这是一条日志信息");
// 释放内存(可选)
delete logger;
return 0;
}
2 要点总结
Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
Singleton 模式一般不要支持拷贝构造函数和 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
如何实现多线程环境下安全的 Singleton?注意对双检查锁的正确实现。