文章目录
什么是单例模式
单例模式(Singleton
Pattern)是一种常用的程序设计模式,属于创建型设计模式的一种。它的核心思想在于控制类的实例化过程,确保在任何情况下,对于特定类来说,在整个应用程序中只存在一个实例,并提供一个全局访问点来获取这个实例。这样做可以确保共享资源的有效管理,减少系统开销,以及在需要全局访问和协调控制时提供便利。
单例模式的关键特点包括:
唯一性:确保一个类只有一个实例。 全局访问点:提供一个全局访问的方法,让其他对象可以轻松地获取到这个唯一的实例。 ** lazy initialization(惰性初始化)或 eager initialization(急切初始化)**:根据实现方式的不同,单例实例可以在类加载时就被创建(饿汉式),也可以在第一次请求实例时才创建(懒汉式)。
实现单例模式通常涉及以下几个方面:
私有构造函数:防止外部直接通过构造函数创建新的实例。 静态实例变量:在类内部声明并初始化单例实例。 公共静态方法:提供一个全局访问点,用于返回这个唯一的实例。
常见的单例实现方式有:
饿汉式:类加载时立即创建实例。 懒汉式:延迟到第一次使用时才创建实例,可能需要考虑线程安全问题。 双重检查锁定(Double-Checked Locking):一种高性能的懒汉式实现,既保证了线程安全,又减少了同步带来的性能损耗。 静态内部类:利用Java类加载机制保证线程安全和延迟初始化。 枚举(Enum):自Java 1.5起,使用枚举实现单例成为一种简洁且线程安全的方式。
实际案例
数据库连接池:在应用程序中,通常只需要一个数据库连接池来管理所有的数据库连接,以避免频繁创建和销毁数据库连接造成的资源浪费。单例模式可以确保整个应用中只有一个数据库连接池实例,所有需要访问数据库的部分都通过这个单例获取连接。
线程池:线程池也是单例模式的一个典型应用场景。一个应用程序通常只需要一个线程池来管理和复用线程,以提高效率和资源利用率。单例模式保证了线程池的唯一性,避免了多线程管理的混乱和资源竞争。
配置管理类:当应用程序的配置信息需要集中管理和访问时,可以使用单例模式设计一个配置管理类。这样做的好处是,无论在应用的哪个部分,都可以轻松获取到相同的配置信息,确保配置的一致性。
日志记录器:日志系统通常要求所有的日志记录都统一管理,以确保日志的连贯性和完整性。通过单例模式实现的日志记录器能够保证所有日志输出到同一份日志文件,避免了多实例导致的日志分散和混乱。
缓存服务:在需要缓存数据的应用中,使用单例模式来管理缓存服务可以确保所有请求都命中同一个缓存实例,避免了数据不一致的问题,同时也简化了缓存的维护和更新逻辑。
设备管理器:在操作系统或硬件相关的应用中,设备管理器常常被设计为单例,因为通常只需要一个中央控制器来管理所有设备的接入、配置和通信,避免了多实例间的冲突和资源争抢。
用户会话管理:在Web应用中,用户会话管理器常常设计为单例,用来跟踪和管理所有用户的会话信息。这样可以集中处理会话超时、会话数据存储等问题,确保会话数据的统一和安全。
单例模式实现步骤
- 构造函数私有化
- 提供一个全局的静态方法,访问唯一对象
- 类中定义一个静态指针,指向唯一对象
懒汉式
c
#include <iostream>
using namespace std;
// 定义一个单例类 SingleTon
class SingleTon
{
public:
// 声明一个静态指针成员变量,用于存储单例对象的地址
static SingleTon* m_singleTon; // 静态指针
// 提供一个静态方法,用于获取单例类的唯一实例
static SingleTon* GetInstance()
{
// 如果单例对象还未被创建(即 m_singleTon 为 NULL)
if (m_singleTon == NULL)
{
// 创建一个新的 SingleTon 实例,并将地址赋值给 m_singleTon
m_singleTon = new SingleTon;
}
// 返回单例对象的地址
return m_singleTon;
}
// 单例类的一个示例方法
void TestPrint()
{
cout << "测试调用....." << endl;
}
private:
// 将构造函数设为私有,防止外部直接创建 SingleTon 的实例
SingleTon() // 构造函数私有化
{
cout << "构造对象......" << endl;
// 初始化静态指针为 NULL,这是一个可选的初始化步骤,在实际的单例模式中通常不需要
m_singleTon = NULL;
}
};
// 在类外初始化静态成员变量 m_singleTon,确保其在程序开始时为 NULL
SingleTon* SingleTon::m_singleTon = NULL;
// 主函数
int main()
{
// 通过 GetInstance() 方法获取单例对象的引用
SingleTon* p1 = SingleTon::GetInstance();
SingleTon* p2 = SingleTon::GetInstance();
// 输出 p1 和 p2 的地址,展示两者指向同一对象
cout << "p1:" << hex << p1 << endl;
cout << "p2:" << hex << p2 << endl;
// 通过 p1 和 p2 调用 TestPrint() 方法,证明它们指向同一个对象
p1->TestPrint();
p2->TestPrint();
return 0; // 程序结束
}
在多线程环境下,如果多个线程恰好同时调用 GetInstance() 方法,可能会创建多个实例。要解决这个问题,可以引入互斥锁(mutex)或其他同步机制来保护实例化的代码块,确保线程安全。
饿汉式
cpp
#include <iostream>
using namespace std;
// 定义一个单例类 SingleTon
class SingleTon
{
public:
// 声明一个静态指针成员变量,用于存储单例对象的地址
static SingleTon* m_singleTon; // 静态指针
// 提供一个静态方法,用于获取单例类的唯一实例
static SingleTon* GetInstance()
{
// 如果单例对象还未被创建(即 m_singleTon 为 NULL)
if (m_singleTon == NULL)
{
// 创建一个新的 SingleTon 实例,并将地址赋值给 m_singleTon
m_singleTon = new SingleTon;
}
// 返回单例对象的地址
return m_singleTon;
}
// 单例类的一个示例方法
void TestPrint()
{
cout << "测试调用....." << endl;
}
private:
// 将构造函数设为私有,防止外部直接创建 SingleTon 的实例
SingleTon() // 构造函数私有化
{
cout << "构造对象......" << endl;
// 初始化静态指针为 NULL,这是一个可选的初始化步骤,在实际的单例模式中通常不需要
m_singleTon = NULL;
}
};
// 在类外初始化静态成员变量 m_singleTon,确保其在程序开始时为 NULL
SingleTon* SingleTon::m_singleTon = NULL;
// 主函数
int main()
{
// 通过 GetInstance() 方法获取单例对象的引用
SingleTon* p1 = SingleTon::GetInstance();
SingleTon* p2 = SingleTon::GetInstance();
// 输出 p1 和 p2 的地址,展示两者指向同一对象
cout << "p1:" << hex << p1 << endl;
cout << "p2:" << hex << p2 << endl;
// 通过 p1 和 p2 调用 TestPrint() 方法,证明它们指向同一个对象
p1->TestPrint();
p2->TestPrint();
return 0; // 程序结束
}
无论后续是否调用 GetInstance() 方法,单例对象都会在程序启动时被创建。这种方法简单且线程安全,但牺牲了一定的资源效率,特别是在单例从未被使用的情况下。
单例模式优缺点
优点:
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
缺点:
- 不适用于变化频繁的对象;
- 如果实例化的对象长时间用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;