设计模式-单例模式
1、界说与概念单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。就像是在一个软件体系中,某些资源大概对象只需要存在一个就可以满意体系的需求,比如体系的配置管理器、数据库毗连池等。通过单例模式,可以制止创建多个实例导致的资源浪费、数据不一致等标题。
2、实现方式
a、懒汉式单例(线程不安全)
[*]代码示例(C++)
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
[*]分析
这种方式在首次调用 getInstance 方法时才创建实例。但是在多线程环境下是不安全的,因为多个线程可能同时判断 instance 为 nullptr,然后都创建一个新的实例,这就违背了单例模式的初衷。
b、懒汉式单例(线程安全)
[*]代码示例(C++)
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex_;
Singleton() {}
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> guard(mutex_);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
[*]分析
在 getInstance 方法中添加了互斥锁(mutex)来保证在多线程环境下只有一个线程可以或许创建实例。lock_guard 是一个 RAII(Resource Acquisition Is Initialization)机制的类,它在构造函数中主动锁定互斥锁,在析构函数中主动解锁,确保了锁的正确使用,制止了死锁等标题。
c、饿汉式单例模式
[*]代码示例(C++)
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
return instance;
}
};
Singleton* Singleton::instance = new Singleton();
[*]分析
这种方式在步伐启动时就创建了单例实例。优点是线程安全,因为在步伐运行时单例实例已经存在,不存在多个线程竞争创建实例的环境。但是可能会导致步伐启动时间变长,因为在初始化阶段就创建了实例,纵然这个实例可能在很长时间之后才会被用到。
3、应用场景
[*] 配置管理类:在一个软件体系中,配置信息通常是全局唯一的。通过单例模式创建一个配置管理类,可以方便地在整个体系中访问和修改配置信息。例如,一个服务器应用步伐可能有一个配置文件,用于存储服务器的端标语、数据库毗连信息等,配置管理类可以以单例模式存在,确保所有模块都能访问到相同的配置信息。
[*] 日志记录器:用于记录体系运行过程中的各种信息,如错误信息、调试信息等。整个体系通常只需要一个日志记录器,这样可以统一管理日志的输特别式、输出位置等。通过单例模式,可以确保在不同的模块中记录的日志都可以或许按照统一的规则进行处理。
[*] 数据库毗连池:在需要频繁访问数据库的应用步伐中,为了制止频繁地创建和销毁数据库毗连,提高性能,可以使用数据库毗连池。毗连池可以以单例模式存在,它负责管理肯定命量的数据库毗连,当有模块需要访问数据库时,从毗连池中获取毗连,使用完毕后再归还毗连,确保体系中只有一个毗连池实例在管理数据库毗连。
4、单例模式优缺点
[*]优点
[*] 资源共享与节约:单例模式确保在整个应用步伐中只有一个实例存在,这对于一些资源密集型的对象(如数据库毗连池、线程池)非常有效。以数据库毗连池为例,创建和维护数据库毗连是比较耗费资源的利用。单例模式下的数据库毗连池可以在整个应用中被共享,制止了频繁创建和销毁毗连,从而节流体系资源,提高性能。
[*] 全局访问点方便使用:单例模式提供了一个全局访问点,使得在应用步伐的任何地方都可以或许方便地获取到该单例对象。例如,在一个复杂的软件体系中,配置管理类作为单例,可以很轻易地被不同模块访问,从而获取体系的配置参数。这种统一的访问方式有助于代码的组织和维护,提高了代码的可读性和可维护性。
[*] 控制共享资源访问:对于一些需要在多个部分之间共享且需要严格控制访问的资源,单例模式可以或许提供有效的管理。例如,在一个日志记录体系中,单例的日志记录器可以确保所有的日志消息都按照统一的格式和存储方式进行处理,制止了多个日志记录实例可能导致的混乱,同时也方便对日志记录的级别、输出位置等进行集中控制。
[*]缺点
[*] 违反单一职责原则:单例类每每会承担过多的职责。因为它在整个体系中是唯一的实例,以是可能会被赋予过多的功能,这就导致了单例类可能会与多个模块产生复杂的交互,使得代码的耦合度增加。例如,一个单例的体系管理类可能既要负责体系的配置管理,又要负责体系的状态监控,这样当体系功能发生变革大概需要扩展时,单例类就会变得难以维护。
[*] 对单位测试不友爱:单例模式在单位测试中可能会带来一些困难。由于单例类的实例是全局唯一的,在测试过程中很难模拟不同的状态大概替换为测试替身(如模拟对象或桩对象)。例如,在测试一个依赖于单例数据库毗连池的模块时,很难隔离数据库毗连池对测试的影响,因为无法轻易地创建一个独立于真实数据库毗连池的测试环境。
[*] 可能导致隐蔽的依赖关系:在应用步伐的多个部分都使用单例对象时,会产生隐蔽的依赖关系。这种依赖关系在代码布局中可能不明显,当需要对单例类进行修改大概替换时,可能会影响到许多依赖它的模块,从而增加了体系的维护成本和风险。例如,一个原本用于本地存储的单例配置管理器,在体系需要支持云端配置存储时,由于很多模块都依赖这个单例配置管理器,修改起来就会比较复杂。
5、多线程环境下怎样保证单例模式的线程安全?
a、双重检查锁定(Double - Checked Locking)机制
[*]原理:
这种方法结合了懒汉式单例的耽误初始化优势和高效的线程安全机制。在第一次检查 if (instance == nullptr) 时,没有加锁,这是为了制止在单例实例已经创建后的每次访问都要获取锁,从而提高性能。只有当实例尚未创建时,才会进入第二次检查,这次检查是在加锁的环境下进行的,确保在多线程环境下只有一个线程可以或许创建实例。
[*]代码示例(以 C++ 为例)
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex_;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> guard(mutex_);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
[*]注意事项:
在 C++ 11 之前,由于编译器和处理器的优化可能会导致双重检查锁定出现标题。因为编译器可能会对指令进行重排序,导致在对象还没有完全初始化时,就将指针赋值给 instance,其他线程可能会访问到未完全初始化的对象。在 C++ 11 中,这种标题得到了肯定程度的缓解,因为新的内存模型保证了在 new 利用时初始化和指针赋值的原子性。不过在一些复杂的编译器和硬件环境下,照旧需要谨慎使用。
b、使用静态局部变量(C++ 11 及以上)
[*]原理:
C++ 11 保证了在多线程环境下,静态局部变量的初始化是线程安全的。当第一次调用 getInstance 函数并访问静态局部变量 instance 时,编译器会主动保证只有一个线程可以或许进行初始化利用,其他线程会等待初始化完成后再访问。这种方式实现起来比较简单,同时也具有懒汉式单例的耽误初始化特点。
[*]代码示例(以 C++ 为例):
//代码实例(线程不安全)
template<typename T>
class Singleton
{
public:
static T& getInstance()
{
static T instance;
return instance;
}
private:
Singleton(){};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};
[*]注意事项:
这种方式的缺点是,假如单例类的构造函数比较复杂大概有依赖关系,可能会导致一些隐蔽的标题。例如,单例类的构造函数中有一些全局变量大概其他单例类的依赖,可能会出现初始化次序的标题。
c、饿汉式单例模式(Eager Initialization)
[*]原理:
在步伐启动时就创建单例实例,这样在多线程环境下就不存在多个线程竞争创建实例的环境。因为单例实例在任何线程访问之前就已经存在,以是是线程安全的。
[*]代码示例(以 C++ 为例):
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
return instance;
}
};
Singleton* Singleton::instance = new Singleton();
[*]注意事项:
饿汉式单例模式的重要缺点是可能会导致步伐启动时间变长,因为在初始化阶段就创建了实例,纵然这个实例可能在很长时间之后才会被用到。而且假如单例类的构造函数抛出异常,可能会导致步伐启动失败,因为在步伐启动时就会实验创建单例实例。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]