1. 程式人生 > >單例模式與Android

單例模式與Android

單例模式(Singleton)

一、  什麼是單例模式

單例模式,簡單點來說就是設計一個類,使其在任何時候,最多隻有一個例項,並提供一個訪問這個例項的全域性訪問點。

二、  為什麼要單例

在程式中的很多地方,只有一個例項是非常重要的。例如,在windows中,工作管理員只有一個,無論你點選多少次開啟工作管理員,工作管理員也只會生成一個視窗。再例如,在一些軟體中,工具箱是唯一的,無論你點選多少次開啟工具箱,工具箱也只一個。

為什麼要這樣設計呢?因為像工作管理員或工具箱這樣的程式,只要有一個就足夠完成所有的工作了,多個程式只會白白消耗系統資源,而像工作管理員這類的程式還會引入多個工作管理員之間的同步問題,所以對些這些程式來說,只有一個例項或程式是必要的。

三、  為什麼需要單例模式

上面講到對於某些程式來說,保持其只有一個例項是必要的,但是如何保證一個程式或一個類只有一個例項呢?下面從類的角度來解說。

第一種方法,我們拋開設計模式這個概念,如果你之前完全不知道這個概念,面對這個設計要求你會怎樣做?我們可以使用一個全域性的類指標變數,初始值為NULL,每當需要建立該類的物件時,都檢查該指標是否為NULL,若為NULL,則使用new建立新的物件,並把物件的指標賦值給該全域性指標變數。若該指標不為NULL,則直接返回該指標或使用該指標。這個可能是最容易想到的方法。

第二種方法,就是使用單例模式。單例模式通過在類內維護一下指向該類的內部的指標,並把其建構函式宣告為private或protected來阻止一般的例項化,而使用一個static的公有成員函式來實現和控制類的例項化。在該static公有成員函式中判斷該類的靜態成員指標是否為NULL,若為NULL,則建立一個新的例項,並把該類的靜態成員指標指向該實現。若該靜態成員指標不為NULL,則直接返回NULL。若這裡看得不是很明白,不要緊,看了下面的類圖和程式碼自會明白。

相比之下,第二種方法比第一種方法好在哪裡呢?首先,第一種做法並沒有強制一個類只能有一個例項,一切的控制權其實在使用者的設計中;而第二種做法,則是類的設計者做好的,與使用者並沒有關係。換句話來說,如果使用第一種做法,則只要使用者願意,該類可以有無數個例項,而對於第二種方法,無論使用都是否願意,它只能有一個例項。這就好比我們去吃飯,第一種方法需要顧客來判斷哪些菜已經賣完,而第二種方法由餐館的判斷哪些菜已經賣完,顯然在生活中,第二種方法才是合理的。

四、  單例模式的類圖


五、  單例模式的實現(C++實現)

1singleton.h,定義類的基本成員及介面

#ifndef SINGLETON_H_INCLUDE
#define SINGLETON_H_INCLUDE
 
class Singleton
{
    public:
       static Singleton*getInstance();
       voidreleaseInstance();
 
    private://function
       Singleton(){}
       ~Singleton(){}
 
    private://data
       static Singleton*_instance;
       static unsigned int_used;
};
#endif

2singleton.cpp,實現getInstance方法和releaseInstance方法

#include "singleton.h"
 
Singleton* Singleton::_instance(0);
unsigned int Singleton::_used(0);
 
Singleton* Singleton::getInstance()
{
    ++_used;
    if(_instance == 0)
    {
       _instance = newSingleton();
    }
    return _instance;
}
 
void Singleton::releaseInstance()
{
    --_used;
    if(_used == 0)
    {
       delete _instance;
       _instance = 0;
    }
}

程式碼分析:

從上面的類圖和程式碼實現可以看到,在單例模式中,我們把類的建構函式宣告為私有的,從而阻止了在類外例項化物件,既然在類外不能例項化物件,那麼我們如何例項化該類呢?我們知道static成員是隨類而存在的,並不隨物件而存在,所以我們利用一個公有的static函式,由它來負責實現化該類的物件,因為該static函式是該類的成員函式,它可以訪問該類的private的建構函式,它也就是我們之前所說的全域性訪問點。

由於可能有多個物件都引用該單例類的物件,而該物件只有一個,所以肯定會有多個指標變數指向堆中同一塊記憶體,若其中一個指標把該堆記憶體delete掉,然而其他的指標並不知道它所引用的物件已經不存在,繼續引用該物件必然會發生段錯誤,為了防止在類的外部呼叫delete,在這裡把解構函式宣告為private,從而讓在類外delete一個指向該單例類物件指標的操作非法。

但是C++的堆記憶體完全由程式設計師來管理,如果不能delete的話,該物件就會在堆記憶體中一直存在,所以在此引入了一個方法releaseInstance和引用計數,當不再需要使用該物件時呼叫releaseInstance方法,該方法會把引用計數減1,當所有程式碼都不需要使用該物件時釋放該物件,即當引用計數為0時,釋放該物件。

六、  多執行緒下的單例模式

上面的程式碼在多執行緒環境下會引發問題,舉個例子,就是當兩個執行緒同時呼叫getInstance函式時,若該類還沒有被例項化,則兩個執行緒讀取到的_instance為0,那麼兩個執行緒都會new一個新的物件,從而讓該類有兩個例項,同時,對_used的操作也會存在相同的問題,此時_use為1。所以,顯然該設計在多執行緒下是不安全的。

為了解決上述問題,我們需要為函式getInstance和releaseInstance中對_instance和_used的訪問加鎖。為了簡便,只列出部分關鍵程式碼,修改後的程式碼如下所示:(原始碼檔案為singlton_thread.h和singleton_thread.cpp)

pthread_mutex_tSingleton::_mutex(PTHREAD_MUTEX_INITIALIZER);
 
Singleton* Singleton::getInstance()
{
    pthread_mutex_lock(&_mutex);
    ++_used;
    if(_instance== 0)
    {
       _instance= new Singleton();
    }
    pthread_mutex_unlock(&_mutex);
    return_instance;
}
 
void Singleton::releaseInstance()
{
    pthread_mutex_lock(&_mutex);
    --_used;
    if(_used== 0)
    {
       delete_instance;
       _instance= 0;
    }
    pthread_mutex_unlock(&_mutex);
}

程式碼分析:

從上面的程式碼可以看出,每次申請呼叫get/releaseInstance函式都會加鎖和解鎖,而加鎖和解鎖都是比較耗時的操作,所以上述的程式碼效率其實並不高。

在一些設計模式的書上,會看到使用雙if的判斷來解決多次上鎖的問題,但是這個方法在這裡是不現實的,因為這個方法不能解決_used的訪問問題,也就是說,即使對_instance的訪問可以使用雙if語句來大大減少加鎖和解鎖的操作,但是對_used的++和--操作同樣需要加鎖進行。而那些書上之所以可以使用雙if來解決這個問題,是由所使用的語言決定的,例如使用java或c#,它們不需要對記憶體進行管理,所以不會存在上面程式碼中所出現的引用計數_used,所以雙if的方法才行得通。

若想在C++中實現雙if的判斷,則不使用引用計數來管理記憶體即可,即物件一旦分配就一直存在於堆記憶體中。此時C++也不存在引用計數問題,不需要釋放記憶體,因而也就不需要上面的releaseInstance方法。其getInstance方法的實現如下:

Singleton* Singleton::getInstance()
{
	if(_instance == 0)
	{
		pthread_mutex_lock(&_mutex);
		if(_instance == 0)
			_instance = new Singleton();
		pthread_mutex_unlock(&_mutex);
	}
	return _instance;
}


這樣就可以只加鎖和解鎖一次,大大提高時間效率。但是物件一旦分配記憶體,記憶體就不會被釋放。所以在C++中使用哪種實現策略,取決於你對時間和空間的取捨,若時間更重要,則採用後一種方法,若空間更重要,則採用前一種方法。

七、  Android中的單例模式

Android中存在著大量的單例類,如:InputMethodManager類,CalendarDatabaseHelper類、Editable類等等。在這些類中,都存在一個方法getInstance,在該方法或直接返回物件的引用或判斷一個類的引用是否為NULL,若不為NULL,則直接返回該引用,若為NULL,則new一個新的物件,並返回。例如,對於CalendarDatabaseHelper類,存在如下的程式碼:

public static synchronized CalendarDatabaseHelper getInstance(Contextcontext)
{
    if (sSingleton == null)
    {
       sSingleton = newCalendarDatabaseHelper(context);
    }
        return sSingleton;
}

從這裡的程式碼可以看出,其實現方式與上面所說的非常相似,不過因為java不用程式設計師自己管理記憶體,所以並不需要使用引用計數,而該方法是公有static的。而synchronized就是為了保證同一時刻只能有一個執行緒進入該方法,這也就是防止上面第六點講到的單例模式在多執行緒中的安全問題。

八、  原始碼地址