面試必備之如何設計線程安全的單例模式

一、定義

      單例模式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 ;
}

 

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