设计模式之单例模式

所有的代码是以c++为准

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

原理:使用静态变量和静态函数

静态变量介绍见c++ static静态变量_baidu_16370559的博客-CSDN博客_c++静态变量

实现过程:假如TaskManager类为需要设计的类

1.为了确保TaskManager实例的唯一性,我们需要禁止类的外部直接使用new来创建对象,将TaskManager的构造函数的可见性改为private

2.虽然类的外部无法再使用new来创建对象,但是在TaskManager的内部还是可以创建的,可见性只对类外有效.定义一个静态的TaskManager类型的私有成员变量.提高封装性。

3.增加一个公有的静态方法,让外界访问到该私有静态成员变量。

 单例模式结构图中只包含一个单例角色:

      ● Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
 

主要优点

       单例模式的主要优点如下:

       (1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

       (2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

       (3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

主要缺点

       单例模式的主要缺点如下:

       (1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

       (2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

       (3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
 

适用场景

       在以下情况下可以考虑使用单例模式:

       (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

       (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
 

延伸思考:

1.如何对单例模式进行改造,使得系统中某个类的对象可以存在有限多个,例如两例或三例?【注:改造之后的类可称之为多例类。】

c++ 代码例子:下面这个例子线程不安全,只适合用于同一线程中。

#pragma once
class CSingleton
{
private:
	CSingleton();
	virtual ~CSingleton();
public:	
	 static CSingleton* GetInstance1();
};
#include "pch.h"
#include "Singleton.h"


CSingleton::CSingleton()
{

}

CSingleton::~CSingleton()
{

}

CSingleton* CSingleton::GetInstance1()
{
	static CSingleton instance;
	return &instance;
}

void testSingletonPattern()
{
	CSingleton *pSingleton1 = CSingleton::GetInstance1();
}

不要通过 申明静态指针,new 的方式建立。这样不好释放内存,并且线程不安全

几种创建方式总结:
1、饿汉式:类初始化的时候,会立即加载该对象,线程天生安全,调用效率高。

2、懒汉式:类初始化时,不会初始化该对象,真正需要使用的时候才会去创建该对象,具备懒加载功能。

3、双重检测方式

4、枚举单例:使用枚举实现单例模式,实现简单、调用效率高,枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞,缺点是没有延迟加载。

5、静态内部类方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

1.饿汉式

优点:实现简单,调用效率高(提前完成初始化),线程安全 

在线程访问单例对象之前就已经创建好了,再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例。

线程安全 

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

缺点: 在不需要的时候可能就加载了,造成内存浪费

class Singleton{
    Singleton() { }
    static Singleton* m_instance_ptr;
public:
    static Singleton* get_instance() {
        return m_instance_ptr;
	}
};
Singleton* Singleton::m_instance_ptr = new Singleton();
int main(){
    Singleton* instance1 = Singleton::get_instance();
    Singleton* instance2 = Singleton::get_instance();
    return 0;
}

2.懒汉式

有2种创建方式

1.new在堆上创建对象

2.静态局部变量,而不是new在堆上创建对象,避免自己回收资源。

优点:只在使用时加载,节省资源

缺点: 存在线程安全问题,如果多个线程同时访问,可能会创建出多个对象。解决办法是:加锁,这样可能会影响效率。

class Singleton
{
public:
  static Singleton* GetInstance()
  {
    Lock(); // not needed after C++11
    static Singleton instance; 
    UnLock(); // not needed after C++11

    return &instance;
  }

private:
  Singleton() {};
  Singleton(const Singleton &);
  Singleton & operator = (const Singleton &);
};

在懒汉模式里,如果大量并发线程获取单例对象,在进行频繁加锁解锁操作时,必然导致效率低下。

3.双重检测方式

可以进一步优化:双重判断的线程安全的懒汉式单例模式,这样既是使用到才初始化,并且线程安全。可行推荐。

///  加锁的懒汉式实现  //

class SingleInstance
{

public:
    // 获取单实例对象
    static SingleInstance *&GetInstance();

    //释放单实例,进程退出时调用
    static void deleteInstance();
	
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal) = delete;
    const SingleInstance &operator=(const SingleInstance &signal)= delete;

private:
    // 唯一单实例对象指针
    static SingleInstance *m_SingleInstance;
    static std::mutex m_Mutex;
};


//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = nullptr;
std::mutex SingleInstance::m_Mutex;

// 注意:返回的是指针的引用
SingleInstance *& SingleInstance::GetInstance()
{

    //  这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleInstance == nullptr) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance == nullptr)
        {
            m_SingleInstance = new (std::nothrow) SingleInstance();
        }
    }

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = nullptr;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}


调用:
SingleInstance *ps1 = SingleInstance::GetInstance();
析构函数调用:SingleInstance::deleteInstance();


3.静态内部类

优点:在需要的时候加载,线程安全

静态内部类:外部类对内部类没有任何优越的访问权限,但是内部类可以访问外部类中的所有成员

public class Singleton05 {
 
    private Singleton05() {
    }
 
    private static class SingletonHolder {
        private static Singleton05 INSTANCE = new Singleton05();
    }
 
    public static Singleton05 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}


在c++不能这样实现。

总结:在c++使用双重判断的线程安全的懒汉式单例模式最合适。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值