设计模式——单例模式(饿汉、懒汉、线程安全)||c++详解

单例模式作为设计模式中最简单的一种,在面试中会被经常问道。在前人的基础上再做精简的总结:

https://blog.csdn.net/QIANGWEIYUAN/article/details/88544524

1.什么是单例模式

单例模式就是有这样一个类,无论你通过何种方式得到多少个该类的对象,结果是,所有的对象都指向该类的唯一一个对象。这就说明,单例模式只能实例化一个对象。我们怎么能做到这一点呢?

有这样一个解决方案:

  1. 在类中定义自己的唯一一个实例对象。
  2. 将构造函数私有化,使类不能在外部实例化。
  3. 通过一个静态的接口返回唯一的一个对象。

人们根据得到单例模式中唯一一个对象的时机不同,形象的将单例模式分为饿汉模式和懒汉模式。

2.饿汉模式

我们先来看一个简单的饿汉模式的代码:

# include<iostream>
using namespace std;
class Single
{
public:
	static Single* GetInstance()
	{
		return &single;
	}
private:
	static Single single;
	Single() { cout << "Construct Single" << endl; }
	~Single() { cout << "Delete Single" << endl; }
	Single(const Single& sing);
};
Single Single::single;//类外初始化
int main()
{
	Single* p1 = Single::GetInstance();
	Single* p2 = Single::GetInstance();
	Single* p3 = Single::GetInstance();
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	return 0;
}

如上述饿汉模式,无论程序用不用得到这个唯一的一个对象,都对这个唯一的对象进行实例化,同时给用户提供GetInstance()接口去得到该对象。

上述程序打印结果如下所示:

可以发现,三个指针指向的都是同一个对象。

3.懒汉模式

懒汉模式,顾名思义,就是,当程序需要这个对象的时候,再去进行实例化。

# include<iostream>
using namespace std;
class Single
{
public:
	static Single* GetInstance()
	{
		if (single == NULL)
		{
			single = new Single();
		}
		return single;
	}
private:
	static Single* single;
	Single() { cout << "Construct Single" << endl; }
	~Single() { cout << "Delete Single" << endl; }
	Single(const Single& sing);
	//为了防止用户忘记delete,或者当多个指针指向对象的时候,delete 产生错误,自定义删除器
	class Release
	{
	public:
		~Release()
		{
			if (single != NULL)
			{
				delete single;
			}
		}
	};
	static Release release;
};
Single* Single::single = nullptr;//类外初始化为空
Single::Release Single::release;//利用对象的自动析构去释放单例对象
int main()
{
	Single* p1 = Single::GetInstance();
	Single* p2 = Single::GetInstance();
	Single* p3 = Single::GetInstance();
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	return 0;
}

运行结果如下:

在上述程序中,为了防止因为delete产生的相关的错误,我们自定义了一个内置类Release,利用自动析构的功能去delete在堆上申请的对象。

4.线程全的单例模式

如上所示,有饿汉模式和懒汉模式两种单例模式。

在饿汉模式中,对象在程序运行之前就已经创建好了,因此不存在线程安全的问题。

在懒汉单例模式中,获取对象的时候要先判断然后进行指针的判断,下来才是对象的内存申请。

single = new Single();这句代码先申请内存,然后调用Singlle的构造函数,最后将给single指针赋值。

那么如果有两个线程:假设为线程1和线程2:

假设线程1先调用GetInstance函数,single=NULL;会进入if语句,如果当线程用new开辟内存的时候,线程1的CPU时间片到了,那么这时线程调用GetInstance函数,也会进行new操作。那么这种情况就不再符合单例模式的要求了。

所以我们要对GetInstace()函数做加锁操作,防止多个线程同时都进入if条件语句的内部。

首先我们先封装一个锁:加头文件pthread.h

class Mutex
{
public:
	Mutex()
	{
		cout << "construct mutex" << endl;
		pthread_mutex_init(&mutex, NULL);
	}
	~Mutex()
	{
		cout << "destory mutex" << endl;
		pthread_mutex_destroy(&mutex);
	}
	void Lock()
	{
		pthread_mutex_lock(&mutex);
	}
	void Unlock()
	{
		pthread_mutex_unlock(&mutex);
	}
private:
	pthread_mutex_t mutex;

};

同时为了提高效率,我们实现带有双重检验锁的线程安全的懒汉模式:

# include<iostream>
using namespace std;
# include<pthread.h>
class Mutex
{
public:
	Mutex()
	{
		cout << "construct mutex" << endl;
		pthread_mutex_init(&mutex, NULL);
	}
	~Mutex()
	{
		cout << "destory mutex" << endl;
		pthread_mutex_destroy(&mutex);
	}
	void Lock()
	{
		pthread_mutex_lock(&mutex);
	}
	void Unlock()
	{
		pthread_mutex_unlock(&mutex);
	}
private:
	pthread_mutex_t mutex;

};
class Single
{
public:
	static Single* GetInstance()
	{
		if (single == NULL)
		{
			mutex.Lock();
			if (single == NULL)
			{
				single = new Single();
			}
			mutex.Unlock();
		}
		return single;
	}
private:
	static Single* single;
	Single() { cout << "Construct Single" << endl; }
	~Single() { 
		cout << "Delete Single" << endl;
	}
	Single(const Single& sing);
	//为了防止用户忘记delete,或者当多个指针指向对象的时候,delete 产生错误,自定义删除器
	class Release
	{
	public:
		~Release()
		{
			if (single != NULL)
			{
				delete single;
			}
		}
	};
	static Release release;
	static Mutex mutex;
};
Single* Single::single = nullptr;//类外初始化为空
Single::Release Single::release;//利用对象的自动析构去释放单例对象
Mutex Single::mutex;//互斥锁的类外初始化
int main()
{
	Single* p1 = Single::GetInstance();
	Single* p2 = Single::GetInstance();
	Single* p3 = Single::GetInstance();
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	return 0;
}

打印信息如下:

符合单例模式的要求:

5.一道面试题

在3中的懒汉模式中,如果我们将代码做如下修改,那么它是线程安全的吗?

# include<iostream>
using namespace std;
class Single
{
public:
	static Single* GetInstance()
	{
		static Single single;
                return &single;
	}
private:
	Single() { cout << "Construct Single" << endl; }
	~Single() { cout << "Delete Single" << endl; }
	Single(const Single& sing);
};

int main()
{
	Single* p1 = Single::GetInstance();
	Single* p2 = Single::GetInstance();
	Single* p3 = Single::GetInstance();
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	return 0;
}

上述单例模式在多线程环境中使用是完全可以的,因为系统对静态变量的初始化会自动进行加锁操作,使静态变量的初始化成为线程安全的操作,因此不会放生多个线程同时初始化single的情况。

发布了124 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42214953/article/details/105362920