前言:在学习c++设计模式后对于单例模式的理解(如有错误的地方,希望大家能够指正)
单例模式顾名思义就是说一个类的对象只有一个,不允许创建多个重复的对象从而来提高性能,单例模式也可以被分类为对象性能模式。
试想以下,如果一个类在被重复创建多次对于系统来说会造成不小的负担,如果只是几个类当然无关紧要,但是一旦数值提升的话那么对于系统来说还是会造成不小的压力
由于笔者对于java中的多线程和锁的学习还没那么深入,在这里的代码片段就是用c++的代码片段来展示
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton * getInstance();
static Singleton* m_instance;
};
//线程非安全
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
{
上面的代码片段就是我们的单例模式,解决了同一个类对象进行反复创建的一个问题,当程序中需要用到Singleton()
这个对象的时候就会先进行判断,如果之前创建过这个对象的话那么就直接返回这个对象,如果没有创建那么就创建一个。
上面这种模式有一个问题,在单线程的情况是没有问题的,但是我们来做一个假设,假设线程A 和 线程 B 同时进入到 if(m_instance == nullptr)
这个判断里面的话,那么就会创建两个对象,就违背了我们单例模式的设计。
对代码进行修改
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton * getInstance();
static Singleton* m_instance;
};
//线程安全,但锁的代价过高
Singleton* Singleton::getInstance(){
Lock lock;
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
{
我们为这个方法加上一个锁,当某个线程进行访问的时候就对这个方法上锁,这样做确实能够解决上面所产生的问题,但是代价太高了,如果说这个对象已经经过了一次创建按道理来说当某个线程来访问这个方法的时候应该直接返回一个对象,但如果按照上述代码来执行的话每当有一个线程来进行访问都会上锁,只有当当前线程拿到了对象之后方法才会打开下一个线程才能够拿到这个对象。
那么我们再来对上面的代码进行修改
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton * getInstance();
static Singleton* m_instance;
};
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){
Lock lock
if(m_instance == nullptr){
m_instance = new Singleton();
}
}
return m_instance;
{
为了解决锁代价过高的问题,引入双检查锁,线程都会先执行if判断,如果对象在之前已经进行过了创建那么就将之前创建过的对象直接进行一个返回,如果没有进行创建进入if方法后进行上锁,然后再进行一次判断,这次判断就能够防止一种情况就是线程A 和线程 B同时进入到了if方法体内然后创建两个对象。
但这种双检查锁也存在一个问题就是会出现一个内存读写reorder的问题导致这个双检查锁失效,当我们执行代码new Singleton()
的时候,在cpu的操作按照逻辑来说应该是
- 先为其分配一个内存地址
- 调用对象的构造函数
- 将创建完成的内存地址进行一个返回
但是在cpu的指令层面上有时候的操作可能会于我们的正常逻辑有一些差异,也就是这个reorder问题
reorder:cpu在执行我们m_instance = new Singleton();
这一行代码的时候有可能是这样操作的
- 为对象分配一个内存地址
- 将内存地址进行一个返回
- 调用对象的构造函数
如果是按照上面这种方法进行执行的话就会出现一个问题,假设线程 A 在执行m_instance = new Singleton();
这段代码的时候cpu跳过了对象的构造器创建阶段 先将内存地址进行了返回,此时线程B访问这个方法,进入if(m_instance == nullptr)
判断的时候此时m_instance
对象已经有了一个内存地址,所以这个判断就会为faluse 方法就会直接将对象返回出去,但此时这个对象虽然说拥有内存地址但是这个对象是没有执行过它的构造方法的,所返回出去的对象是不能够正常的进行使用的。
当然上面这个问题也有解决的办法,笔者在这里就不进行赘述,原理大概就是通过一些关键字来让cpu按照我们预想的逻辑顺序执行对应的操作来解决reorder问题。