单例模式介绍

        单例模式是一种常见的设计模式,用于确保某个类只能被实例化一次,从而保证系统中某个特定类的唯一性。(也就是说这个类不管创建多少次对象,永远只能得到该类型一个对象的实例)单例模式常被用于控制资源的访问权限,例如数据库连接池、线程池等资源池的管理。

基本步骤:

1.构造函数私有化;

2.唯一的类的实例化对象(静态成员变量);

3. 静态方法,用来获取唯一实例化的对象的接口方法。 

  在实现单例模式时通常有两种方式:

1.懒汉式单例模式:

        懒汉式单例模式指的是在需要使用单例对象时才进行实例化,以避免不必要的资源浪费。该模式通常会在类内部维护一个静态的私有对象指针当需要实例化对象时,如果该指针为空,就创建一个新的对象并赋值给该指针,否则直接返回已有的对象。

class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
private:
    Singleton() {}  // 构造函数私有化,避免外部实例化对象
    static Singleton* instance;  // 静态私有指针,指向唯一的对象
};



Singleton* Singleton::instance = nullptr;  // 静态指针初始化为空

int main() {
    Singleton* obj1 = Singleton::getInstance();
    Singleton* obj2 = Singleton::getInstance();
    // obj1 和 obj2 指向同一个对象
    return 0;
}

        在这个示例中,Singleton 类被设计为单例模式,getInstance() 方法返回唯一的对象指针。该方法在第一次被调用时会创建一个新的对象,并将其赋值给静态的私有指针 instance,以后每次调用该方法都会返回已有的对象指针。

2.饿汉式单例模式:

        饿汉式单例模式指的是在程序启动时就实例化单例对象,以避免多线程环境下的竞争问题。该模式通常会在类内部维护一个静态的私有对象,并在定义时就进行初始化,以保证对象的唯一性。

以下是一个简单的饿汉式单例模式的示例:

class Singleton {
public:
    static Singleton* getInstance() {
        return instance;
    }

private:
    Singleton() {}  // 构造函数私有化,避免外部实例化对象
    static Singleton* instance;  // 静态私有指针,指向唯一的对象
};

Singleton* Singleton::instance = new Singleton();  // 静态指针初始化为对象

int main() {
    Singleton* obj1 = Singleton::getInstance();
    Singleton* obj2 = Singleton::getInstance();
    // obj1 和 obj2 指向同一个对象
    return 0;
}

        构造函数私有化意味着只有类的内部成员可以访问构造函数,外部无法通过实例化类的方式来创建对象,从而实现了对对象的控制。

        由于构造函数是私有化的,因此这个新的实例只能在Singleton类的内部被创建。这样,就保证了Singleton类的唯一实例只有一个,且只能通过getInstance()函数获取。

        

线程安全的单例模式问题:

  1. 饿汉式单例模式线程安全特性

        一定是线程安全的; 因为静态的成员变量在main之前已经创建好了

        缺点:对象实例化的时候会调用构造函数,这个构造函数可能会执行比如说加载配置信息、读取磁盘文件、和数据库连接等操作,这个过程应该放在我们第一次需要访问这个实例对象的时候,就是说如果我门初始化好了这个对象但是我们在该函数中没有用到这个对象,这个过程就浪费掉资源了.

      2. 懒汉式单例模式线程安全特性

static CSingleton* getInstance()
{
	if (nullptr == single)
	{
		single = new CSingleton();
	}
	return single;
}

 很明显,这个getInstance是个不可重入函数,也就它在多线程环境中执行,会出现竞态条件问题,首先搞清楚这句代码,single = new CSingleton()它会做三件事情,开辟内存,调用构造函数,给single指针赋值,那么在多线程环境下,就有可能出现如下问题:

1. 线程A先调用getInstance函数,由于single为nullptr,进入if语句
2. new操作先开辟内存,此时A线程的CPU时间片到了,切换到B线程
3. B线程由于single为nullptr,也进入if语句了,开始new操作

        很明显,上面两个线程都进入了if语句,都试图new一个新的对象,不符合单例模式的设计,那该如何处理呢?对了,应该为getInstance函数内部加锁,在线程间进行互斥操作。此处介绍Linux系统下,pthread库中提供的线程互斥操作方法-mutex互斥锁,代码如下:

#include <iostream>
#include <pthread.h>
using namespace std;

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		// 获取互斥锁
		pthread_mutex_lock(&mutex);
		if (nullptr == single)
		{
			single = new CSingleton();
		}
		// 释放互斥锁
		pthread_mutex_unlock(&mutex);
		return single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() 
	{ 
		pthread_mutex_destroy(&mutex); // 释放锁
		cout << "~CSingleton()" << endl; 
	}
	CSingleton(const CSingleton&);

	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	static CRelease release;
	
	// 定义线程间的互斥锁
	static pthread_mutex_t mutex;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;
// 互斥锁的初始化
pthread_mutex_t CSingleton::mutex = PTHREAD_MUTEX_INITIALIZER;

int main()
{
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	return 0;
}

         上面的代码,是一个线程安全的懒汉单例模式,但是明眼人都能看出来,效率太低,因为每次调用getInstance都需要加锁解锁。

        a) 锁+双重判断:

        其实除了第一次调用,后面对getInstance函数持续的加解锁实在时没有必要,所以这里需要使用锁+双重判断,也叫双重检验锁,把上面的getInstance函数代码修改如下:

static CSingleton* getInstance()
{
	if (nullptr == single)
	{
		// 获取互斥锁
		pthread_mutex_lock(&mutex);
		/* 
		这里需要再添加一个if判断,否则当两个
		线程都进入这里,又会多次new对象,不符合
		单例模式的涉及
		*/
		if(nullptr == single)
		{
			single = new CSingleton();
		}
		// 释放互斥锁
		pthread_mutex_unlock(&mutex);
	}
	
	return single;
}

        b)定义实例化对象的过程放到接口函数中,作为函数静态局部变量:

#include <iostream>
using namespace std;

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		static CSingleton single; // 懒汉式单例模式,定义唯一的对象实例
		return &single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl;}
	CSingleton(const CSingleton&);
};
int main()
{
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	return 0;
}

         对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值