【设计模式】【01单例模式】

发布于:2024-05-21 ⋅ 阅读:(79) ⋅ 点赞:(0)

可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501


目录

系列文章目录

1.简介

1.1 单例模式的类型

1.2 单例模式的特点

2.饿汉式单例

3.懒汉式单例

3.1 有缺陷的懒汉式单例

3.2 线程安全的懒汉式单例

4.懒汉式单例和饿汉式单例的区别

4.1 初始化方面

4.2 线程安全方面

4.3 资源消耗方面

4.4 性能方面


1.简介

单例模式是一个类有且仅有一次在内存中创建对象。当程序多次创建调用同一个类的对象且作用相同时,为了避免多次创建对象消耗资源,单例模式便出现了。单例模式可以让内存中一个类仅有一个实例,并让所有调用者共享这个实例。

1.1 单例模式的类型

1.懒汉式单例:在类加载的时候创建对象,天生是线程安全的。

2.俄汉式单例:在第一次调用的时候创建对象。

1.2 单例模式的特点

1.单例类只能有一个实例

2.单例类必须自己创建自己的唯一实例

3.单例类必须给所有其他对象提供这个一实例

单例模式的写法有好几种,这里主要介绍两种:懒汉式单例、饿汉式单例。

2.饿汉式单例

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

class Singleton
{
public:
    static Singleton *getInstance();

private:
    Singleton();
    static Singleton *mSingletonInstance;
};
//在类加载的时候,实例化自己
Singleton *Singleton::mSingletonInstance = new Singleton();

Singleton *Singleton::getInstance()
{
    return mSingletonInstance;
}

Singleton::Singleton() {}

int main()
{

    Singleton *instance1 = Singleton::getInstance();
    printf("instance1 = %p\n", instance1);

    Singleton *instance2 = Singleton::getInstance();
    printf("instance2 = %p\n", instance2);

    return 0;

}

由于饿汉式单例会在类加载的时候创建静态对象,如果此对象没有使用,则会浪费一定的内存。因此,产生了懒汉式单例。

3.懒汉式单例

在第一次调用的时候初始化对象。

3.1 有缺陷的懒汉式单例

#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

class Singleton
{
public:
    static Singleton *getInstance();

private:
    Singleton();
    static Singleton *mSingletonInstance;
};

Singleton *Singleton::mSingletonInstance = nullptr;

Singleton *Singleton::getInstance()
{
    if (mSingletonInstance == nullptr)
    {

        mSingletonInstance = new Singleton();
    }
    return mSingletonInstance;
}

Singleton::Singleton() {}

int main()
{

    Singleton *instance1 = Singleton::getInstance();
    printf("instance1 = %p\n", instance1);

    Singleton *instance2 = Singleton::getInstance();
    printf("instance2 = %p\n", instance2);

    return 0;
}

打印结果:

instance1 = 0x55555556aeb0
instance2 = 0x55555556aeb0

此时在单线程中时安全的,但是在多线程中,是不安全的,因为可能存在两个线程同时调用getInstance方法,同时走进mSingletonInstance == nullptr的判断,此时两个线程会创建两个对象,这与单例的概念不符合。

那么如何解决呢?

加锁。

3.2 线程安全的懒汉式单例

#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

class Singleton
{
public:
    static Singleton *getInstance();
    static std::mutex mSingletonLock;

private:
    Singleton();
    static Singleton *mSingletonInstance;
};

Singleton *Singleton::mSingletonInstance = nullptr;
std::mutex Singleton::mSingletonLock;

Singleton *Singleton::getInstance()
{
    std::lock_guard<std::mutex> lock(mSingletonLock);
    if (mSingletonInstance == nullptr)
    {
        mSingletonInstance = new Singleton();
    }
    return mSingletonInstance;
}

Singleton::Singleton() {}

int main()
{

    Singleton *instance1 = Singleton::getInstance();
    printf("instance1 = %p\n", instance1);

    Singleton *instance2 = Singleton::getInstance();
    printf("instance2 = %p\n", instance2);

    return 0;
    
}

我们加锁后,保证了线程安全,但是每一次获取单例对象时,都要加锁,会影响多线程下的性能。

思路:我们只需要在第一次初始化对象的时候加锁,在后续的获取对象时不用加锁。

解决办法:双重判定。

#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

class Singleton
{
public:
    static Singleton *getInstance();
    static std::mutex mSingletonLock;

private:
    Singleton();
    static Singleton *mSingletonInstance;
};

Singleton *Singleton::mSingletonInstance = nullptr;
std::mutex Singleton::mSingletonLock;

Singleton *Singleton::getInstance()
{
    if (mSingletonInstance == nullptr)
    {
        std::lock_guard<std::mutex> lock(mSingletonLock);
        if (mSingletonInstance == nullptr)
        {
            mSingletonInstance = new Singleton();
        }
    }

    return mSingletonInstance;
}

Singleton::Singleton() {}

int main()
{

    Singleton *instance1 = Singleton::getInstance();
    printf("instance1 = %p\n", instance1);

    Singleton *instance2 = Singleton::getInstance();
    printf("instance2 = %p\n", instance2);

    return 0;
}

这样就避免了每次调用都加锁的情况、,同时在第一次调用的时候加锁,保证了只会创建一个对象。

4.懒汉式单例和饿汉式单例的区别

4.1 初始化方面

1.饿汉式会在类加载时,就初始化完成,保证getInstance的时候,单例是已经存在的了。

2.懒汉式只有当第一次i调用getInstance的时候,才会初始化这个单例。

4.2 线程安全方面

1.饿汉式天生就是线程安全的,可以直接用于多线程。

2.懒汉式本身是非线程安全的,需要通过加锁来保证。

4.3 资源消耗方面

1 .饿汉式在类创建的时候就会实例化一个静态对象出来,此对象不管是否使用都会占据内存。

2.懒汉式只有在第一次使用该单例的时候才会实例化对象出来。因此不会浪费内存资源。

4.4 性能方面

1.饿汉式在类创建的时候就会实例化一个静态对象出来,因此在在第一次调用时速度会更快,因为其资源已经初始化完成。

2.懒汉式只有在第一次使用该单例的时候才会实例化对象出来,因此第一次调用要初始化,所以偏慢。



网站公告

今日签到

点亮在社区的每一天
去签到