单例模式(Singleton Pattern) 是一种常见的设计模式,它确保一个类只有一个实例,并提供全局访问点来获取该实例。换句话说,单例模式保证某个类在整个应用程序生命周期中只有一个实例,并提供一个全局的访问接口来获取该实例。
单例模式的关键特点:
- 唯一性:类只有一个实例。
- 全局访问:可以通过一个全局的静态方法访问到这个实例。
- 延迟初始化:单例实例的创建通常是懒加载的(即在第一次使用时才创建实例)。
单例模式的应用场景:
- 需要控制某个类的实例数量(例如,配置管理器、日志记录器、数据库连接池等)。
- 需要确保全局只有一个对象,并且该对象需要被多个地方共享。
单例模式的实现方式:
1. 懒汉式(Lazy Singleton):延迟实例化
这种方式只有在第一次访问时才创建实例,延迟到真正需要时才创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Singleton { private: static Singleton* instance;
Singleton() {}
public: static Singleton* getInstance() { if (instance == nullptr) { instance = new Singleton(); } return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
Singleton* Singleton::instance = nullptr;
|
- 优点:延迟实例化,只有在需要时才创建实例。
- 缺点:线程不安全,如果在多线程环境中并发访问
getInstance,可能会导致创建多个实例。
2. 饿汉式(Eager Singleton):提前实例化
这种方式在程序启动时就创建实例,确保在整个生命周期中只有一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Singleton { private: static Singleton* instance;
Singleton() {}
public: static Singleton* getInstance() { return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
Singleton* Singleton::instance = new Singleton();
|
- 优点:实例在程序启动时就创建,线程安全。
- 缺点:即使实例未被使用,也会在程序启动时创建。
3. 线程安全的懒汉式(Double-Checked Locking)
为了在多线程环境下保证单例的唯一性,使用锁机制来保证线程安全,同时避免每次访问时都进行锁操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Singleton { private: static Singleton* instance; static std::mutex mtx;
Singleton() {}
public: static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mtx); if (instance == nullptr) { instance = new Singleton(); } } return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
Singleton* Singleton::instance = nullptr; std::mutex Singleton::mtx;
|
- 优点:在多线程环境下确保只创建一个实例,性能较好。
- 缺点:稍微复杂,涉及锁机制。
4. 静态局部变量(C++11及以上)
利用静态局部变量,C++11标准保证静态局部变量的初始化是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Singleton { private: Singleton() {}
public: static Singleton& getInstance() { static Singleton instance; return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
|
- 优点:简单,线程安全,不需要显式的锁。
- 缺点:实例是静态的,无法被销毁(在应用程序退出时才会被销毁)。
使用单例模式的注意事项:
- 全局访问点:单例模式提供全局访问点,但如果滥用会让代码变得难以测试和维护,因为任何地方都可以改变单例的状态。
- 线程安全:如果在多线程环境中使用,必须确保实例的创建过程是线程安全的。
- 内存泄漏:如果单例模式使用动态分配内存(例如
new),需要确保在程序结束时正确释放资源,防止内存泄漏。
什么时候使用单例模式:
- 需要一个共享资源或者全局状态,且只需要一个实例(例如,日志管理、配置管理器)。
- 想避免多次创建相同的对象,并且需要全局访问该对象。