设计模式:创建型模式之单例模式(c++)

简介

       单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  • 4、在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现

单例模式要做如下事情:

  • 不能通过构造函数构造,否则就能够实例化多个。构造函数需要私有声明
  • 保证只能产生一个实例

实现单例模式的思路是一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

       当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

        通常单例模式有两种构建方式:

  • 懒汉方式。指全局的单例实例在第一次被使用时构建。
  • 饿汉方式。指全局的单例实例在类装载时构建。

 通过单例模式, 可以做到:

  1. 确保一个类只有一个实例被建立 
  2. 提供了一个对对象的全局访问指针 
  3. 在不影响单例类的客户端的情况下允许将来有多个实例

单例模式在多线程的应用场合下必须小心使用:

  1. 如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
  2. getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

 

懒汉式单例

懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。懒汉式是” 时间换空间“。

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为C++的构造函数不是线程安全的,所以上述代码在多线程的情况下是不安全的,原因是new Singelton时,这句话不是原子的,比如一个线程执行了new的同时,另一个线程对if进行判断(此时实例还没被创建出来)将出错。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

class Singleton {  
    private:
     static Singelton *single; 
     Singleton(){}  
  
    public: 
     static Singelton *GetSingelton(); {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
     }  
}

Singelton *Singelton::single = nullptr;
int Singelton::m_count = 0;

在windows下模拟:

#include <iostream>
#include <process.h>
#include <windows.h>
using namespace std;
 
class Singelton{
private:
    Singelton(){
        m_count ++;
        printf("Singelton begin\n");
        Sleep(1000);                            // 加sleep为了放大效果
        printf("Singelton end\n");
    }
    static Singelton *single;
public:
    static Singelton *GetSingelton();
    static void print();
    static int m_count;
};
 
Singelton *Singelton::single = nullptr;
int Singelton::m_count = 0;
 
Singelton *Singelton::GetSingelton(){
    if(single == nullptr){
        single = new Singelton;
    }
    return single;
}
 
void Singelton::print(){
    cout<<m_count<<endl;
}
// 回调函数
void threadFunc(void *p){
    DWORD id = GetCurrentThreadId();        // 获得线程id
     cout<<id<<endl;
    Singelton::GetSingelton()->print();      // 构造函数并获得实例,调用静态成员函数
}
 
int main(int argc, const char * argv[]) {
    int threadNum = 3;
    HANDLE threadHdl[100];
     
    // 创建3个线程
    for(int i = 0; i<threadNum; i++){
        threadHdl[i] = (HANDLE)_beginthread(threadFunc, 0, nullptr);
    }
     
    // 让主进程等待所有的线程结束后再退出
    for(int i = 0; i<threadNum; i++){
        WaitForSingleObject(threadHdl[i], INFINITE);
    }
    cout<<"main"<<endl;                 // 验证主进程是否是最后退出
    return 0;
}

运行结果:

 

饿汉式单例

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。这种方式比较常用,但容易产生垃圾对象。饿汉式就是“空间换时间”。(本身就是线程安全的)

  • 优点:没有加锁,执行效率会提高。
  • 缺点:类加载时就初始化,浪费内存。

它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

class Singleton {  
    private:
     static Singelton *single; 
     Singleton(){}  
  
    public: 
     static Singelton *GetSingelton(); { 
        // 不再需要进行实例化 
        //if (instance == null) {  
        //    instance = new Singleton();  
        //}  
        //return instance;  
     }  
}

// 饿汉模式的关键:初始化即实例化
Singelton *Singelton::single = new Singelton;
int Singelton::m_count = 0;

在windows下模拟:

#include <iostream>
#include <process.h>
#include <windows.h>
using namespace std;
 
class Singelton{
private:
    Singelton(){
        m_count ++;
        printf("Singelton begin\n");
        Sleep(1000);                            // 加sleep为了放大效果
        printf("Singelton end\n");
    }
    static Singelton *single;
public:
    static Singelton *GetSingelton();
    static void print();
    static int m_count;
};
// 饿汉模式的关键:初始化即实例化
Singelton *Singelton::single = new Singelton;
int Singelton::m_count = 0;
 
Singelton *Singelton::GetSingelton(){
    // 不再需要进行实例化
    //if(single == nullptr){
    //    single = new Singelton;
    //}
    return single;
}
 
void Singelton::print(){
    cout<<m_count<<endl;
}
// 回调函数
void threadFunc(void *p){
    DWORD id = GetCurrentThreadId();        // 获得线程id
     cout<<id<<endl;
    Singelton::GetSingelton()->print();      // 构造函数并获得实例,调用静态成员函数
}
 
int main(int argc, const char * argv[]) {
    int threadNum = 3;
    HANDLE threadHdl[100];
     
    // 创建3个线程
    for(int i = 0; i<threadNum; i++){
        threadHdl[i] = (HANDLE)_beginthread(threadFunc, 0, nullptr);
    }
     
    // 让主进程等待所有的线程结束后再退出
    for(int i = 0; i<threadNum; i++){
        WaitForSingleObject(threadHdl[i], INFINITE);
    }
    cout<<"main"<<endl;                 // 验证主进程是否是最后退出
    return 0;
}

运行结果: 

饿汉式会提前浪费我们的内存空间以及资源,如果有项目中要求我们在使用到实例的时候再去实例化,则还是需要使用线程安全型懒汉式。

 

多线程控制

C++编程语言中,单例模式应用的例子如下述代码所示(这里仅仅提供一个示例;由于加入了锁,这代码是线程安全的)

// ...

class lock
{
    public:
        lock();
        lock(lock const & l);
        ~lock();
        lock & operator =(lock const & l);
        void request();
        void release();
    // ...
};

lock::lock()
{
    // ...
}

// ...

lock::~lock()
{
    // ...
}

// ...

void lock::request()
{
    // ...
}

void lock::release()
{
    // ...
}

// ...

// assumes _DATA_TYPE_ has a default constructor
template<typename _DATA_TYPE_>
class singleton
{
    public:
        static _DATA_TYPE_ * request();
        static void release();

    private:
        singleton();
        singleton(singleton<_DATA_TYPE_> const & s);
        ~singleton();
        singleton<_DATA_TYPE_> & operator =(singleton<_DATA_TYPE_> const & s);
        static _DATA_TYPE_ * pointer;
        static lock mutex;
    // ...
};

template<typename _DATA_TYPE_>
_DATA_TYPE_ * singleton<_DATA_TYPE_>::pointer = nullptr;

template<typename _DATA_TYPE_>
lock singleton<_DATA_TYPE_>::mutex;

template<typename _DATA_TYPE_>
_DATA_TYPE_ * singleton<_DATA_TYPE_>::request()
{
    if(singleton<_DATA_TYPE_>::pointer == nullptr)
    {
        singleton<_DATA_TYPE_>::mutex.request();

        if(singleton<_DATA_TYPE_>::pointer == nullptr)
        {
            singleton<_DATA_TYPE_>::pointer = new _DATA_TYPE_;
        }

        singleton<_DATA_TYPE_>::mutex.release();
    }

    return singleton<_DATA_TYPE_>::pointer;
}

template<typename _DATA_TYPE_>
void singleton<_DATA_TYPE_>::release()
{
    if(singleton<_DATA_TYPE_>::pointer != nullptr)
    {
        singleton<_DATA_TYPE_>::mutex.request();

        if(singleton<_DATA_TYPE_>::pointer != nullptr)
        {
            delete singleton<_DATA_TYPE_>::pointer;
 
            singleton<_DATA_TYPE_>::pointer = nullptr;
        }

        singleton<_DATA_TYPE_>::mutex.release();
    }
}

template<typename _DATA_TYPE_>
singleton<_DATA_TYPE_>::singleton()
{
    // ...
}

// ...

int main()
{
    int * s;

    s = singleton<int>::request();

    // ...

    singleton<int>::release();

    return 0;
}

在windows下模拟线程安全的懒汉式单例的实现(windows下IDE请确保安装Pthread库,教程传送们):

#include <iostream>
#include <process.h>
#include <windows.h>
#include <mutex>
#include <cstdio>

#define HAVE_STRUCT_TIMESPEC
#include <pthread.h>

using namespace std;

class singleton
{
protected:
	singleton()
	{
		// 初始化
		pthread_mutex_init(&mutex, NULL);
		m_count++;
		printf("Singelton begin\n");
		Sleep(1000);                            // 加sleep为了放大效果
		printf("Singelton end\n");
	}

private:
	static singleton* p;

public:
	static pthread_mutex_t mutex;
	static singleton* initance();
	static void print();
	static int m_count;
};

int singleton::m_count = 0;
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;


singleton* singleton::initance()
{
	if (p == NULL)
	{
		// 加锁
		pthread_mutex_lock(&mutex);
		if (p == NULL)
			p = new singleton();
		pthread_mutex_unlock(&mutex);
	}
	return p;
}

void singleton::print() {
	//cout << "This is Print the m_count:  " << m_count << endl;
	cout << m_count << endl;
}
// 回调函数
void threadFunc(void *p) {
	DWORD id = GetCurrentThreadId();        // 获得线程id
	cout << id << endl;
	singleton::initance()->print();      // 构造函数并获得实例,调用静态成员函数
}

int main(int argc, const char * argv[]) {
	int threadNum = 3;
	HANDLE threadHdl[100];

	// 创建3个线程
	for (int i = 0; i < threadNum; i++) {
		threadHdl[i] = (HANDLE)_beginthread(threadFunc, 0, nullptr);
	}

	// 让主进程等待所有的线程结束后再退出
	for (int i = 0; i < threadNum; i++) {
		WaitForSingleObject(threadHdl[i], INFINITE);
	}
	cout << "main" << endl;                 // 验证主进程是否是最后退出
	return 0;
}

 

 

参考资料:

https://wiki.tw.wjbk.site/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F

https://www.jianshu.com/p/69eef7651667

https://www.cnblogs.com/xuelisheng/p/9744301.html

https://www.runoob.com/design-pattern/singleton-pattern.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值