单例模式
是一种创建型设计模式1,它保证一个类在整个系统运行期间只有一个实例,并且提供一个全局访问点来访问这个唯一实例。
无论在系统的任何地方、任何时间,对该类进行实例化操作,获取到的都是同一个对象实例。这就像在一个公司中,通常会有一个唯一的总经理,无论从哪个部门去获取总经理这个角色的实例,得到的都是同一个人。
一般通过将类的构造函数设置为私有,防止外部代码通过常规的new
操作符来创建多个实例。只有类自身内部可以创建实例,并且会在类的内部保存这个唯一的实例,确保不会再创建其他实例。
单例模式提供了一个全局访问点,使得系统中的任何其他模块或类都可以方便地访问到这个唯一的实例。就好比在一个城市中,有一个唯一的市政服务中心,城市中的各个机构、企业和居民都可以通过特定的渠道(比如地址、电话等)来访问这个市政服务中心,获取所需的服务或信息。在软件系统中,这个全局访问点通常是一个静态方法或属性,通过类名就可以直接调用,方便了不同模块之间对单例对象的共享和使用。
原理:在类加载时候就立即创建单例实例,不管是否需要使用该实例,该模式是线程安全2的。
示例代码:
#include
class Singleton {
private:
// 私有静态实例,在类加载时就创建
static Singleton* instance;
// 私有构造函数,防止外部实例化
Singleton() {}
// 拷贝构造函数和赋值运算符重载设为私有,防止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 公共的静态方法,用于获取单例实例
static Singleton* getInstance() {
return instance;
}
void showMessage() {
std::cout << "This is a singleton instance." << std::endl;
}
};
// 静态成员变量需要在类外初始化
Singleton* Singleton::instance = new Singleton();
int main() {
Singleton* singleton1 = Singleton::getInstance();
Singleton* singleton2 = Singleton::getInstance();
if (singleton1 == singleton2) {
std::cout << "Both pointers point to the same instance." << std::endl;
}
singleton1->showMessage();
return 0;
}
代码解释:
instance
是一个私有静态指针,在类加载时就被初始化为一个Singleton
对象的地址。new
创建新的实例。getInstance
方法返回存储的单例实例。原理:在第一次需要使用单例实例时才创建,延迟了实例化的时间,该模式是非线程安全的。
示例代码:
#include
class Singleton {
private:
// 私有静态实例,初始化为null
static Singleton* instance;
// 私有构造函数,防止外部实例化
Singleton() {}
// 拷贝构造函数和赋值运算符重载设为私有,防止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 公共的静态方法,用于获取单例实例
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void showMessage() {
std::cout << "This is a singleton instance." << std::endl;
}
};
// 静态成员变量初始化为null
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* singleton1 = Singleton::getInstance();
Singleton* singleton2 = Singleton::getInstance();
if (singleton1 == singleton2) {
std::cout << "Both pointers point to the same instance." << std::endl;
}
singleton1->showMessage();
return 0;
}
代码解释:
instance
初始化为nullptr
。getInstance
方法在第一次调用时检查instance
是否为nullptr
,如果是则创建新实例。为了解决懒汉式单例模式在多线程环境下的问题,可以使用互斥锁来保证线程安全。
示例代码:
#include
#include
class Singleton {
private:
// 私有静态实例,初始化为null
static Singleton* instance;
// 互斥锁,用于线程同步
static std::mutex mutex_;
// 私有构造函数,防止外部实例化
Singleton() {}
// 拷贝构造函数和赋值运算符重载设为私有,防止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 公共的静态方法,用于获取单例实例
static Singleton* getInstance() {
std::lock_guard lock(mutex_);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void showMessage() {
std::cout << "This is a singleton instance." << std::endl;
}
};
// 静态成员变量初始化为null
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
int main() {
Singleton* singleton1 = Singleton::getInstance();
Singleton* singleton2 = Singleton::getInstance();
if (singleton1 == singleton2) {
std::cout << "Both pointers point to the same instance." << std::endl;
}
singleton1->showMessage();
return 0;
}
代码解释:
std::mutex
作为互斥锁,确保在多线程环境下只有一个线程可以创建实例。std::lock_guard
用于自动管理锁的生命周期,在进入getInstance
方法时加锁,离开时自动解锁。原理:Meyers 单例模式是由 Scott Meyers 提出的一种单例模式实现方式,它利用了 C++ 静态局部变量的特性。在 C++11 及以后的标准中,静态局部变量的初始化是线程安全的,即当多个线程同时首次调用包含静态局部变量的函数时,会保证静态局部变量只被初始化一次。
示例代码
#include
class Singleton {
private:
// 私有构造函数,防止外部实例化
Singleton() {}
// 拷贝构造函数和赋值运算符重载设为私有,防止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 公共的静态方法,用于获取单例实例
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
void showMessage() {
std::cout << "This is a singleton instance." << std::endl;
}
};
int main() {
Singleton& singleton1 = Singleton::getInstance();
Singleton& singleton2 = Singleton::getInstance();
if (&singleton1 == &singleton2) {
std::cout << "Both references refer to the same instance." << std::endl;
}
singleton1.showMessage();
return 0;
}
代码解释
new
操作符来创建Singleton
类的实例。delete
,防止通过拷贝或赋值操作创建新的实例。getInstance
**方法:该方法返回一个Singleton
类的引用。在方法内部,使用static
关键字声明了一个局部变量instance
,这个变量会在第一次调用getInstance
方法时进行初始化,并且只会初始化一次。之后每次调用getInstance
方法都会返回这个已经初始化好的实例的引用。getInstance
方法时才创建单例实例,实现了延迟加载。这样可以避免在程序启动时创建不必要的对象,节省系统资源。但在多线程环境下,如果不进行同步处理,可能会出现多个线程同时创建实例的问题。getInstance
方法时创建实例,同样实现了延迟加载。不过它利用了 C++ 静态局部变量的特性,保证了线程安全的初始化。getInstance
方法,并且此时实例还未创建,可能会导致多个线程都判断instance
为nullptr
,从而创建多个实例,破坏了单例的唯一性。为了保证线程安全,需要使用锁机制进行同步,但这会带来一定的性能开销。getInstance
方法时才会创建实例,提高了资源的利用率。ConfigManager
类来管理所有的配置信息,不同的业务模块可以通过调用ConfigManager.getInstance().getConfig(key)
方法来获取相应的配置项。UserPreferences
类来管理用户的播放设置、收藏列表等信息,当用户在设置界面修改了偏好后,其他界面可以立即获取到最新的设置。Logger
类,通过调用Logger.getInstance().setLevel(Level.DEBUG)
方法来设置全局的日志级别。饿汉式单例模式在类加载时就创建实例,其特点是线程安全,无需额外的同步机制,但可能会造成资源的提前占用。
懒汉式单例模式在第一次使用时才创建实例,实现了延迟加载,避免了不必要的资源浪费,但在多线程环境下需要额外的同步机制来保证线程安全。
Meyers 单例模式结合了懒汉式的延迟加载和线程安全的特性,利用 C++ 静态局部变量的初始化机制,无需额外的同步操作,实现简单且性能较高。
getInstance
方法来获取唯一的实例,确保了资源管理器在整个游戏引擎中的唯一性和线程安全性。创建型设计模式主要用于处理对象的创建过程,包括对象的实例化、对象的创建方式以及对象创建的时机等。常见的创建型设计模式有:单例模式、工厂模式、建造者模式、圆形模式等。 ↩︎
在多线程环境中,多个线程可能会同时访问和修改共享资源(如全局变量、静态变量、堆上分配的对象等)。由于线程的执行顺序是不确定的,可能会导致以下几种情况,从而引发线程安全问题:
竞态条件:多个线程对共享资源进行读写操作时,由于执行顺序的不确定性,最终的结果依赖于线程的执行顺序,这种情况称为竞态条件。例如,两个线程同时对一个计数器变量进行加 1 操作,由于线程调度的原因,可能会导致计数器的值只增加了 1 而不是 2。
数据不一致:当一个线程正在修改共享资源时,另一个线程可能会读取到修改过程中的中间状态,从而导致数据不一致。比如,一个线程正在更新一个数组的元素,而另一个线程同时读取这个数组,可能会读取到部分更新的数组。 ↩︎