单例模式

Channing Hsu

1. 什么是单例设计模式

单例模式是一种创建型设计模式, 它的核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。

  • 只有一个实例的意思是,在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象。
  • 全局访问点的意思是,为了让其他类能够获取到这个唯一实例,该类提供了一个全局访问点(通常是一个静态方法),通过这个方法就能获得实例。

2. 为什么要使用单例设计模式呢

简易来说,单例设计模式有以下几个优点让我们考虑使用它:

  • 全局控制:保证只有一个实例,这样就可以严格的控制客户怎样访问它以及何时访问它,简单的说就是对唯一实例的受控访问(引用自《大话设计模式》第21章)

  • 节省资源:也正是因为只有一个实例存在,就避免多次创建了相同的对象,从而节省了系统资源,而且多个模块还可以通过单例实例共享数据。

  • 懒加载:单例模式可以实现懒加载,只有在需要时才进行实例化,这无疑会提高程序的性能。

3. 基本要求

想要实现一个单例设计模式,必须遵循以下规则:

  • 私有的构造函数:防止外部代码直接创建类的实例

  • 私有的静态实例变量:保存该类的唯一实例

  • 公有的静态方法:通过公有的静态方法来获取类的实例

image-20240104113824985

4. 实现

单例模式的实现方式有多种,包括懒汉式、饿汉式等。

饿汉式指的是在类加载时就已经完成了实例的创建,不管后面创建的实例有没有使用,先创建再说,所以叫做
“饿汉”。

而懒汉式指的是只有在请求实例时才会创建,如果在首次请求时还没有创建,就创建一个新的实例,如果已经创建,就返回已有的实例,意思就是需要使用了再创建,所以称为“懒汉”。

在多线程环境下,由于饿汉式在程序启动阶段就完成了实例的初始化,因此不存在多个线程同时尝试初始化实例的问题,但是懒汉式中多个线程同时访问 getInstance() 方法,并且在同一时刻检测到实例没有被创建,就可能会同时创建实例,从而导致多个实例被创建,这种情况下我们可以采用一些同步机制,例如使用互斥锁来确保在任何时刻只有一个线程能够执行实例的创建。

举个例子,你和小明都发现家里没米了,在你们没有相互通知的情况下,都会去超市买一袋米,这样就重复购买了,违背了单例模式。

下面以Java的代码作为实例,说明单例设计模式的基本写法:

  1. 饿汉模式:实例在类加载时就被创建, 这种方式的实现相对简单,但是实例有可能没有使用而造成资源浪费。
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
26
27
28
29
#include <iostream>
#include <memory>
using namespace std;

class Singleton{
public:
// 禁止拷贝构造和赋值操作,防止复制实例
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static shared_ptr<Singleton> getInstance(){
return instance;
}
void print(){
cout << "Singleton" << endl;
}
private:
Singleton(){
cout << " private Singleton()" << endl;
}
static shared_ptr<Singleton> instance;
};

shared_ptr<Singleton> Singleton::instance = nullptr;
int main() {
shared_ptr<Singleton> s1 = Singleton::getInstance();
s1->print();
return 0;
}

  1. 懒汉模式:第一次使用时才创建
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
26
27
28
29
30
31
32
#include <iostream>
#include <memory>

using namespace std;

class Singleton{
public:
static shared_ptr<Singleton> getInstance(){
if (!instance) {
instance = shared_ptr<Singleton>(new Singleton());
}
return instance;
}
void showMessage() {
cout << "这是单例模式调用的函数" << endl;
}

private:
Singleton(){
cout << "私有构造函数" << endl;
}
static shared_ptr<Singleton> instance;

};
shared_ptr<Singleton> Singleton::instance = nullptr;
int main() {
shared_ptr<Singleton> s1 = Singleton::getInstance();
s1->showMessage();

return 0;
}

5. 什么时候使用单例设计模式

那在什么场景下应该考虑使用单例设计模式呢?可以结合单例设计模式的优点来看。

  1. 资源共享

多个模块共享某个资源的时候,可以使用单例模式,比如说应用程序需要一个全局的配置管理器来存储和管理配置信息、亦或是使用单例模式管理数据库连接池。

  1. 只有一个实例

当系统中某个类只需要一个实例来协调行为的时候,可以考虑使用单例模式, 比如说管理应用程序中的缓存,确保只有一个缓存实例,避免重复的缓存创建和管理,或者使用单例模式来创建和管理线程池。

  1. 懒加载

如果对象创建本身就比较消耗资源,而且可能在整个程序中都不一定会使用,可以使用单例模式实现懒加载。

在许多流行的工具和库中,也都使用到了单例设计模式,比如Java中的Runtime类就是一个经典的单例,表示程序的运行时环境。此外 Spring 框架中的应用上下文 (ApplicationContext) 也被设计为单例,以提供对应用程序中所有 bean 的集中式访问点。

评论