设计模式之单例模式

何谓单例模式?

在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就是单例模式。一个典型应用就是任务队列。

创建一个实例我们有如下方法:

  1. 构造函数
  2. 拷贝构造函数
  3. 赋值构造函数
  4. 移动构造函数
  5. 移动赋值函数

我们只需要一个实例, 那么就要限制获取实例的途径

  1. 将构造函数私有化,在类内部只调用一次。
  2. 其他函数声明用 = delete 修饰

注意:

  • 在类外部不能调用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,把这个静态对象的访问权限设置为私有的
  • 在类中,静态成员函数才能访问其静态成员变量,所以给这个单例类提供一个静态函数用于得到这个静态的单例对象

单例模式的类的示例代码

因此,定义一个单例模式的类的示例代码如下:

// 定义一个单例模式的类
class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance();
private:
	Singleton() = default;
	static Singleton* instance_;
}

饿汉模式

所谓饿汉模式是在类加载的时候立即进行实例化,这样就得到了唯一的实例。

注意:类的静态成员变量在使用之前必须在类的外部进行初始化

class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance() {
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
}

// 初始化
Singleton* Singleton::instance_ = new Singleton;

int main() {
	......
	Singleton* ins = Singleton::get_instance();
	......
}

懒汉模式

懒汉模式一如其名“懒得不行”,在类加载的时候并不进行对象的实例化,而是等到需要的时候再进行实例化。这可能会引发线程安全问题!

下面这段代码在单线程下没有问题,但在多线程环境下将出现线程安全问题。

class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance() {
		if (instance_ == nulllptr) {
			instance_ = new Singleton;
		}
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;

int main() {
	......
	// 此时进行实例化
	Singleton* ins = Singleton::get_instance();
	......
}

试想在多线程环境下,同时有两个线程调用了 get_instance(),它们看到的instance_ 都是nullptr,那么就会创建出两个实例!怎么解决呢?

互斥锁保护

class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance() {
		m_instance_.lock();
		if (instance_ == nulllptr) {
			instance_ = new Singleton;
		}
		m_instance_.unlock();
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
	static mutex m_instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;
mutex Singleton::m_instance_;

这种简单使用互斥锁虽然可以解决线程安全问题,但是获取实例都需要上锁检查(不仅第一次创建实例时需要上锁,即使已经创建了实例仍然需要上锁检查),效率实在低下!

双重检查锁定

通过两个嵌套的if来判断单例对象是否为空的操作叫做双重检查锁定

我们改进的思路就是在加锁、解锁的代码块外层再添加一个判断,这样当任务队列的实例被创建出来之后,访问这个对象的线程就不会再执行加锁和解锁操作了。

class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance() {
		if (instance_ == nulllptr) {
			m_instance_.lock();
			if (instance_ == nullptr) {
				instance_ = new Singleton;
			}
			m_instance_.unlock();
		}
		return instance_;
	}
private:
	Singleton() = default;
	static Singleton* instance_;
	static mutex m_instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;
mutex Singleton::m_instance_;

万万没想到——可能会使用未创建好的实例?

到此仍然没有大功告成,因为万万没想到机器指令会造成麻烦!
假设线程A执行到instance_ = new Singleton,线程B执行到第一个if (instance_ == nulllptr)

instance_ = new Singleton在执行过程中对应的机器指令可能会被重新排序。
正常过程如下:

  • 分配内存
  • 构造对象
  • 指针指向分配的内存

但是被重新排序以后执行顺序可能会变成这样:

  • 分配内存
  • 指针指向分配的内存。
  • 构造对象

线程B在进行指针判断的时候instance_ 指针是不为空的,但这个指针指向的内存却没有被初始化!

怎么解决呢?使用atomic 原子操作(不可中断的操作)。

class Singleton {
public:
	Singleton(const Singleton& oth) = delete;
	Singleton& operator=(const Singleton& oth) = delete;
	Singleton(Singleton&& oth) = delete;
	Singleton& operator=(Singleton&& oth) = delete;

	static Singleton* get_instance() {
		Singleton* obj = instance_.load();
		if (obj== nulllptr) {
			m_instance_.lock();
			obj = instance_.load();
			if (obj == nullptr) {
				obj = new Singleton();
				instance_.store(obj);
			}
			m_instance_.unlock();
		}
		return obj;
	}
private:
	Singleton() = default;
	static mutex m_instance_;
	static atomic<Singleton*> instance_;
}
atomic<Singleton*> Singleton::instance_;
mutex Singleton::m_instance_;

相关推荐

  1. 【前端设计模式模式

    2024-06-06 20:56:02       63 阅读
  2. 设计模式模式

    2024-06-06 20:56:02       57 阅读
  3. C++设计模式模式

    2024-06-06 20:56:02       58 阅读
  4. 设计模式模式

    2024-06-06 20:56:02       54 阅读
  5. 设计模式模式

    2024-06-06 20:56:02       63 阅读
  6. 设计模式模式

    2024-06-06 20:56:02       53 阅读
  7. 【软件设计模式模式

    2024-06-06 20:56:02       57 阅读
  8. c++设计模式模式

    2024-06-06 20:56:02       47 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-06 20:56:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-06 20:56:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-06 20:56:02       87 阅读
  4. Python语言-面向对象

    2024-06-06 20:56:02       96 阅读

热门阅读

  1. Android R及以上版本中APP外部存储实现

    2024-06-06 20:56:02       29 阅读
  2. word模板内容替换

    2024-06-06 20:56:02       28 阅读
  3. python字典包连接mysql

    2024-06-06 20:56:02       33 阅读
  4. 从list的模拟实现中了解迭代器的设计方式

    2024-06-06 20:56:02       38 阅读
  5. PHP面向对象编程总结

    2024-06-06 20:56:02       29 阅读
  6. 【ES】docker安装ES7.14.0+es-head

    2024-06-06 20:56:02       28 阅读
  7. 22data-脚本 6.18-6.21

    2024-06-06 20:56:02       29 阅读
  8. 分享一款提取抖音小店商家电话的软件使用教程

    2024-06-06 20:56:02       117 阅读