面试必备之如何设计线程安全的单例模式

一、定义

      单例模式Singleton:是一种创建模式,使用单例模式可以保证一个类在程序空间只会生成唯一的一个实例。

      GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。

二、应用场景

1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件 
2. Windows的Task Manager(任务管理器)就是很典型的单例模式。
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。 
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。 
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 
 7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。 
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 
10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例. 

三、具体实现

UML类图:

1.饿汉式:在需要实例化对象的时候创建对象。

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

class Singleton{
private:
	static Singleton *instance;
	Singleton(){};
    //防止拷贝构造和赋值操作
	Singleton(const Singleton &obj) { ;}
	Singleton& operator=(const Singleton &obj)	{ ;}
public:
	static Singleton *getInstance(){
		if(instance == NULL){//当第一次访问没有创建对象时,创建对象
			instance = new Singleton;
		}
		return instance;
	}
};
int main(){
	Singleton *singleton1 = Singleton::getInstance();
	Singleton *singleton2 = Singleton::getInstance();
	if(singleton1 == singleton2){
	    cout<<"相同对象"<<endl;
	}else{
	    cout<<"相同对象"<<endl;
	}
	return 0 ;
}

2.懒汉式:类初始化的时候就创建好对象,调用时直接返回。

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

class Singleton{
private:
	static Singleton *instance;
	Singleton(){};
    //防止拷贝构造和赋值操作
	Singleton(const Singleton &obj) { ;}
	Singleton& operator=(const Singleton &obj)	{ ;}
public:
	static Singleton *getInstance(){
		//if(instance == NULL){//当第一次访问没有创建对象时,创建对象
		//	instance = new Singleton;
		//}
		return instance;//直接返回对象
	}
};
Singleton *Singleton::instance = new Singleton;
int main(){
	Singleton *singleton1 = Singleton::getInstance();
	Singleton *singleton2 = Singleton::getInstance();
	if(singleton1 == singleton2){
	    cout<<"相同对象"<<endl;
	}else{
	    cout<<"相同对象"<<endl;
	}
	return 0 ;
}

3.线程安全的单例模式

(1)存在的问题:

1."懒汉"模式虽然有优点,但是每次调用GetInstance()静态方法时,必须判断NULL == instance,使程序相对开销增大。

2.多线程中会导致多个实例的产生,从而导致运行代码不正确以及内存的泄露。

3.“饿汉”模式提前创建好对象,虽然存在线程安全问题,但若始终没有使用该对象时,存在资源浪费情况。

(2)为什么会存在线程不安全问题:

这是因为C++中构造函数并不是线程安全的。

   C++中的构造函数简单来说分两步:

        第一步:内存分配

        第二步:初始化成员变量

        由于多线程的关系,可能当我们在分配内存好了以后,还没来得急初始化成员变量,就进行线程切换,另外一个线程拿到所有权后,由于内存已经分配了,但是变量初始化还        没进行,因此打印成员变量的相关值会发生不一致现象。

(3)怎样实现线程安全的单例模式

在程序进行并发时,需要对共享资源进行同步操作,以保证数据的一致性,Windows提供了四种同步操作: 

 事件对象(Event):
      
事件对象作为标志在线程间传递信号。一个或多个线程可等待一个事件对象,当指定的事件发生时,事件对象通知等待线程可以开始执行。它有两种类型:自动重置(auto-reset)事件和手动重置(manual-reset)事件。


 临界区(Critical Section):
   
  临界区对象通过提供一个进程内所有线程必须共享的对象来控制线程。只有拥有那个对象的线程可以访问保护资源。在另一个线程可以访问该资源之前,前一个线程必须释放临界区对象,以便新的线程可以索取对象的访问权。


  互斥量(Mutex Semaphore):
     
 互斥量的工作方式非常类似于临界区,只是互斥量不仅保护一个进程内为多个线程使用的共享资源,而且还可以保护系统中两个或多个进程之间的的共享资源。


  信号量(Semaphore):
     
 信号量可以允许一个或有限个线程访问共享资源。它是通过计数器来实现的,初始化时赋予计数器以可用资源数,当将信号量提供给一个线程时,计数器的值减1,当一个线程释放它时,计数器值加1。当计数器值小于等于0时,相应线程必须等待。信号量是Windows98同步系统的核心。从本质上讲,互斥量是信号量的一种特殊形式。

本例使用互斥量实现:(原理上线程同步时,临界区比互斥量耗时少,但这里为了方便,用互斥量实现)

#include<stdio.h>
#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;

class Singleton{
private:
	static Singleton *instance;
	static HANDLE mutex;
	Singleton(){
	    cout<<"Single new ..."<<endl;
	};
	//防止拷贝构造和赋值操作
	Singleton(const Singleton &obj) { ;}
	Singleton& operator=(const Singleton &obj)	{ ;}

public:
	static Singleton *getInstance(){
		if(instance == NULL){
            //若没有互斥,则Sigleton会被对次创建
			WaitForSingleObject(mutex,INFINITE);

			if(instance == NULL){//double check lock,对instance进行两次检查,好处在于这个代码只会被第一个线程执行一次
				instance = new Singleton;
			}
			ReleaseMutex(mutex);
		}
		return instance;//返回对象
	}
};
HANDLE Singleton::mutex = CreateMutex(NULL,0,NULL);
Singleton * Singleton::instance = NULL;
UINT WINAPI fun(LPVOID lpPara){

	Singleton::getInstance();
	return 0 ;
}
int main(){
	HANDLE handle[10];
	for(int i = 0 ; i < 10 ;i++){
		handle[i] = (HANDLE)_beginthreadex(NULL,0,fun,NULL,0,NULL);
	}
	WaitForMultipleObjects(10,handle,true,INFINITE);
	return 0 ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章