前言

上一篇文章中(c++单例模式与线程安全(一)),简单的介绍了单例模式的概念以及两种实现方式(懒汉和饿汉)。这里首先回顾一下懒汉和饿汉的概念。
懒汉:在第一次使用时创建实例。
饿汉:在类加载时创建实例。

线程安全分析

试想一下,如果有多线程的情况下,两种单例模式是否安全?是否会出现创建多个实例的情况呢?下面先来看一下饿汉方式的单例模式,在此之前,我再把饿汉方式的实现代码拷贝过来,有助于分析。

#include <iostream>
using namespace std;
class Config
{
	private:
		Config(){cout << "config()" << endl;}
		private: static Config* temp;
	public:
		~Config(){cout << "~Config()" << endl;}
		void ReadConfig(){cout << "ReadConfig()" << endl;}
		void WriteConfig(){cout << "WriteConfig()" << endl;}
		public:
		static Config* getInstance()
		{
			return temp;
		}
};
Config* Config::temp = new Config();//创建实例
int main()
{
	cout << "main running" << endl;
	Config* a = Config::getInstance();
	Config* b = Config::getInstance();
	return 0;
}

很明显,饿汉是线程安全的。因为饿汉模式下实例很早就创建了,假设在main中创建了两个线程去获取实例,两个线程返回的实例都会是最早创建的那个实例,因此不存在会创建多个实例的情况。
再来看一下懒汉方式下的线程安全,同样的,先拷贝一份懒汉模式的代码以便分析。

#include <iostream>
using namespace std;
class Config
{
	private:
		Config(){cout << "config()" << endl;}
		private: static Config* temp;
	public:
		~Config(){cout << "~Config()" << endl;}
		void ReadConfig(){cout << "ReadConfig()" << endl;}
		void WriteConfig(){cout << "WriteConfig()" << endl;}
		public:
		static Config* getInstance()
		{
			if(temp == NULL)
				temp = new Config();
			return temp;
		}
};
Config* Config::temp = NULL;
int main()
{
	Config* a = Config::getInstance();
	Config* b = Config::getInstance();
	return 0;
}

我们假设有两个线程需要获取config的实例。当线程1调用getInstance(),并且判断temp == NULL为真,因为此时还没有实例,此时切到线程2也同样调用了getInstance(),并且也判断了temp== NULL为真,然后线程2创建了一个实例,再切回到了线程1 ,这样线程1也创建了实例,可以看出在两个线程下,懒汉有肯能会创建两个实例。

线程安全的单例模式

如何避免这种情况呢?也很简单,加锁就可以了,锁是常用的使线程同步的一种方式。

#include <iostream>
#include <pthread.h> 
using namespace std;
class Config
{
	private:
			static Config* temp;
			static pthread_mutex_t mutex;//定义一个锁
		Config()
		{
			cout << "config()" << endl;
			pthread_mutex_init(&mutex,NULL);//构造函数中初始化锁,属性为空,表示互斥锁
		}
	public:
		~Config()
		{
			cout << "~Config()" << endl;
		} 
		void ReadConfig(){cout << "ReadConfig()" << endl;}
		void WriteConfig(){cout << "WriteConfig()" << endl;}
		public:
		static Config* getInstance()
		{
			pthread_mutex_lock(&mutex);//进入临界区 加锁
			if(temp == NULL)
				temp = new Config();
			pthread_mutex_unlock(&mutex);//出临界区 解锁
			return temp;
		}
};
Config* Config::temp = NULL;
pthread_mutex_t Config::mutex;
int main()
{
	cout << "main running" << endl;
	Config* a = Config::getInstance();
	Config* b = Config::getInstance();
	return 0;
}

互斥锁是比较常用的一种锁,在访问全局变量时之前加锁,访问完之后解锁,这样可以保证多线程中的全局变量是同步的。采用上述加锁的代码之后,我们再来分析一下两个线程的情况。
首先线程1调用getInstance()函数并且对mutex加锁,判断temp == NULL为真,进入条件语句中,此时切到线程2,也调用getInstance()函数,由于mutex已经被线程1锁定,线程2不持有mutex,所以线程2会阻塞pthread_mutex_lock这个函数中,然后又切回到线程1,线程1创建了一个单例的实例,并且解锁,再切回到线程2,线程2给mutex加锁,再判断temp == NULL,这个条件为假,因为temp的值已经被线程1给赋值了,所以直接返回了temp,即线程1创建的实例,再解锁。这样就满足了在多线程的情况下也不会创建多个实例,满足单例模式的设计思想。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐