浅谈设计模式:单例模式

单例模式(Singleton Pattern)

单例模式顾名思义就是类只有一个实例的模式。当在实际场景中只能存在一个唯一对象时,我们无法保证用户只创建了一个实例对象,尤其是在对系统功能的版本迭代过程中,不同的开发人员可能会创建多个实例对象,这与系统的逻辑可能是不符的。因此需要由类限制实例对象创建的行为。单例模式正是这样一种设计模式,在GOF中其定义如下:

保证类仅有一个实例,并提供一个访问它的全局访问点。

那么问题来了,如何在类内限制实例对象的创建?

主要在于两点:(1)构造函数以及类成员对象的访问权限;(2)在创建实例时需要判断是否已经有实例对象创建

基本思路:

  • 要限制用户创建对象的行为,用户不能直接调用构造函数实例化对象,因此构造函数要设置为private;
  • 由于类只能有一个实例,因此在类内定义一个该类类型的成员变量,此外用static关键字来设置全局特性;
  • 提供一个public接口给用户,该接口中会判断是否已经有实例化的对象,若没有则调用构造函数进行对象的实例,由于在创建实例对象时需要对类进行判断,因此不能通过对象调用该接口,因此需要用static修饰;

根据上面的思路,可以得到单例模式的结构图,如下:

接下来给一段代码样例:

//单例模式之懒汉模式
class Singleton
{
private:
    static Singleton* s_instance;
    
    Singleton();

public:
    static Singleton* GetInstance();
}

Singleton* Singleton::s_instance= nullptr;    //初始化

Singleton::Singleton()
{

}

// 用户创建实例
Singleton* Singleton::GetInstance()
{
    if(nullptr == s_instance)
    {
        s_instance= new Singleton();
    }
}

上面创建的类可以实现单例模式,这种创建方式被称作懒汉模式。但是如果有多个线程同时调用了GetInstance方法来实例化对象,可能会出现一个线程已经实例化对象,而另外的线程没有及时得知,又重新创建了实例,此时就破坏了“单例”这一特性。因此这种单例模式是线程不安全的。

那么要实现线程安全,最容易想到的方法是加锁,利用双重锁机制设置临界区,使得在多线程情况下仍能保证只能创建了一个实例,修改后的代码如下:

//懒汉模式(双检锁)
class Singleton
{
private:
    static Singleton* s_instance;
    
    Singleton();

public:
    static std::mutex s_mutex1;
    static std::mutex s_mutex2;
    static Singleton* GetInstance();
}

Singleton* Singleton::s_instance = nullptr;    //初始化
std::mutex Singleton::s_mutex1;
std::mutex Singleton::s_mutex2;

Singleton::Singleton()
{

}

// 用户创建实例
Singleton* Singleton::GetInstance()
{
    if(nullptr == s_instance)
    {
        std::lock_guard<std::mutex> lock1(s_mutex1);
        if(nullptr == s_instance)
        {
            std::lock_guard<std::mutex> lock2(s_mutex2);
            instance = new Singleton();
        }
    }
}

利用双检锁的懒汉模式可以较好地实现单例模式下的线程安全。此外我们看到懒汉模式实例化对象的特点,只有当需要用到实例时才进行实例化。

除了懒汉模式之外,还有一种单例模式,饿汉模式,代码如下:

//单例模式之饿汉模式
class Singleton
{
private:
    static Singleton* s_instance;
    
    Singleton();

public:
    static Singleton* GetInstance();
}

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

Singleton::Singleton()
{

}

// 用户访问实例
Singleton* Singleton::GetInstance()
{
    return s_instance;
}

从上面的代码可以看出,饿汉模式中在全局中就进行了实例化,提前占用了系统资源,而GetInstance只是用户访问该实例的一个接口,因此是线程安全的。那么懒汉模式和饿汉模式都能够实现线程安全,在实际开发过程中要如何选择呢?

懒汉模式:只有在需要使用类实例时才进行实例化,因此在访问量较小时使用,以时间换取空间。

饿汉模式:不管是否需要使用类实例,在全局中就先进行实例化,因此在访问量较大,线程较多的情况下使用,以空间换取时间。

最后给出一个可以直接运行的饿汉模式的实例,可以根据此实例进行一个修改

#include <iostream>
#include <string>
//饿汉模式
class SingletonInstance
{
private:
    SingletonInstance() {};

    static SingletonInstance *s_instance;

public:
    static SingletonInstance* getInstance()
    {
        return s_instance;
    }
    
};

SingletonInstance* SingletonInstance::s_instance= new SingletonInstance();

int main()
{
    SingletonInstance *instance1 = SingletonInstance::getInstance();
    SingletonInstance *instance2 = SingletonInstance::getInstance();

    if (instance1 == instance2)
    {
        std::cout << "instance1 & instance2: Same." << std::endl;
    }
    else
    {
        std::cout << "instance1 & instance2: Different." << std::endl;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值