DesignPattern 1 单例模式

一、Singleton 单例模式

  • 定义:

    单例模式(Singleton Pattern,也称为单件模式),使用最广泛的设计模式之一。单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。

  • 使用场景:

    在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动
    程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用
    的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

  • 特点:

  1. 单例类只有一个实例对象;
  2. 构造函数和析构函数为私有类型,即该单例对象必须由单例类自行创建;
  3. 拷贝构造函数和赋值构造函数是私有类型,保证实例的唯一性;
  4. 单例类对外提供一个访问该单例的全局访问点,一个静态方法。
  • 优点:
  1. 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  2. 保证程序的线程安全,可以避免对资源的多重占用。
  3. 单例模式设置全局访问点,可以优化和共享资源的访问。
  • 缺点
  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背
    开闭原则。
  2. 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

1. 懒汉式-线程不安全

线程安全问题仅出现在第一次初始化(new)过程中

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() {}; //构造和析构 私有化
~Singleton() {};
Singleton(const Singleton&); //拷贝构造和赋值构造 私有化
const Singleton& operator=(const Singleton&);

public:
static Singleton* getInstance()
{
if (instance == NULL)
instance = new Singleton();

return instance;
}
};

Singleton* Singleton::instance = nullptr;
Singleton* instance_1 = Singleton::getInstance();
Singleton* instamce_2 = Singleton::getInstance();
  • 获取了两次实例,但是只有一个构造函数被调用,即只生成了唯一实例。
  • 可能存在的问题:线程安全:实例化竞争;内存泄漏:只有构造没有析构

2. 加锁的懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SingleInstance_1
{
public:
static SingleInstance_1*& getInstance();
static void deleteInstance();

private:
SingleInstance_1();
~SingleInstance_1();

SingleInstance_1(const SingleInstance_1& signal) = delete;
const SingleInstance_1& operator=(const SingleInstance_1& signal) = delete;

static SingleInstance_1* m_Instance_1;
static std::mutex mutex;
};
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
SingleInstance_1* SingleInstance_1::m_Instance_1 = nullptr;
std::mutex SingleInstance_1::mutex;

SingleInstance_1*& SingleInstance_1::getInstance()
{
if (SingleInstance_1::m_Instance_1 == nullptr)
{
std::unique_lock<std::mutex> lock(SingleInstance_1::mutex);
if (SingleInstance_1::m_Instance_1 == nullptr)
{
SingleInstance_1::m_Instance_1 = new (std::nothrow) SingleInstance_1();
}
}

return m_Instance_1;
}

void SingleInstance_1::deleteInstance()
{
std::unique_lock<std::mutex> lock(SingleInstance_1::mutex);
if (SingleInstance_1::m_Instance_1)
{
delete m_Instance_1;
m_Instance_1 = nullptr;
}
}

SingleInstance_1::SingleInstance_1() { std::cout << "create\n "; };
SingleInstance_1::~SingleInstance_1() { std::cout << "delete\n"; };

3. 加锁的懒汉式 - 智能指针

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
33
34
35
class SingleInstance_2
{
public:
static std::shared_ptr<SingleInstance_2> getInstance();
~SingleInstance_2()
{
std::cout << this << std::endl;
}

private:
SingleInstance_2()
{
std::cout << this << std::endl;
}

SingleInstance_2(const SingleInstance_2& single) = delete;
const SingleInstance_2& operator=(const SingleInstance_2& single) = delete;
};

static std::shared_ptr<SingleInstance_2> m_Instance = nullptr;
static std::mutex mutex;

std::shared_ptr<SingleInstance_2> SingleInstance_2::getInstance()
{
if (m_Instance == nullptr)
{
std::unique_lock<std::mutex> lock(mutex);
if (m_Instance == nullptr)
{
auto temp = std::shared_ptr<SingleInstance_2>(new SingleInstance_2());
m_Instance = temp;
}
}
return m_Instance;
}

4. 推荐的懒汉式 - 静态局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SingleInstance_3
{
public:
~SingleInstance_3() { std::cout << this << std::endl; };
SingleInstance_3(const SingleInstance_3&) = delete;
const SingleInstance_3& operator=(const SingleInstance_3&) = delete;

static SingleInstance_3& getInstance()
{
static SingleInstance_3 m_Instance;
return m_Instance;
}
private:
SingleInstance_3() { std::cout << this << std::endl; };
};

//SingleInstance_3& instance = SingleInstance_3::getInstance()
  • 通过局部静态变量的特性保证了线程安全
  • 不需要使用共享指针,代码简洁
  • 如果采用返回指针实现而不是返回引用实现,无法避免用户使用delete instance导致对象被提前销毁

5. 饿汉式

相比于懒汉式而言,代码一运行就初始化创建实例 ,本身就线程安全

1
2
3
// 对象在程序执行时优先创建
// 源码首行加上
Singeleton* Singeleton::m_Instance = new (std::nothrow) Singleton();

std::nothrow

普通new一个异常的类型std::bad_alloc,机在内存分配失败的时候直接报异常。在内存不足时,new (std::nothrow) 并不抛出异常,而是将指针置NULL,因此 std::nothrow 可以实现对非零指针的检查。