設(shè)計(jì)模式系列——單例模式-Singleton Pattern

1 單例模式的動(dòng)機(jī)

對(duì)于一個(gè)軟件系統(tǒng)的某些類(lèi)而言,我們無(wú)須創(chuàng)建多個(gè)實(shí)例。舉個(gè)大家都熟知的例子——Windows任務(wù)管理器,如圖3-1所示,我們可以做一個(gè)這樣的嘗試,在Windows的“任務(wù)欄”的右鍵彈出菜單上多次點(diǎn)擊“啟動(dòng)任務(wù)管理器”,看能否打開(kāi)多個(gè)任務(wù)管理器窗口?如果你的桌面出現(xiàn)多個(gè)任務(wù)管理器,我請(qǐng)你吃飯,

(注:電腦中毒或私自修改Windows內(nèi)核者除外)。通常情況下,無(wú)論我們啟動(dòng)任務(wù)管理多少次,Windows系統(tǒng)始終只能彈出一個(gè)任務(wù)管理器窗口,也就是說(shuō)在一個(gè)Windows系統(tǒng)中,任務(wù)管理器存在唯一性。為什么要這樣設(shè)計(jì)呢?我們可以從以下兩個(gè)方面來(lái)分析:其一,如果能彈出多個(gè)窗口,且這些窗口的內(nèi)容完全一致,全部是重復(fù)對(duì)象,這勢(shì)必會(huì)浪費(fèi)系統(tǒng)資源,任務(wù)管理器需要獲取系統(tǒng)運(yùn)行時(shí)的諸多信息,這些信息的獲取需要消耗一定的系統(tǒng)資源,包括CPU資源及內(nèi)存資源等,浪費(fèi)是可恥的,而且根本沒(méi)有必要顯示多個(gè)內(nèi)容完全相同的窗口;其二,如果彈出的多個(gè)窗口內(nèi)容不一致,問(wèn)題就更加嚴(yán)重了,這意味著在某一瞬間系統(tǒng)資源使用情況和進(jìn)程、服務(wù)等信息存在多個(gè)狀態(tài),例如任務(wù)管理器窗口A顯示“CPU使用率”為10%,窗口B顯示“CPU使用率”為15%,到底哪個(gè)才是真實(shí)的呢?這純屬“調(diào)戲”用戶,

,給用戶帶來(lái)誤解,更不可取。由此可見(jiàn),確保Windows任務(wù)管理器在系統(tǒng)中有且僅有一個(gè)非常重要。

圖3-1 Windows任務(wù)管理器

回到實(shí)際開(kāi)發(fā)中,我們也經(jīng)常遇到類(lèi)似的情況,為了節(jié)約系統(tǒng)資源,有時(shí)需要確保系統(tǒng)中某個(gè)類(lèi)只有唯一一個(gè)實(shí)例,當(dāng)這個(gè)唯一實(shí)例創(chuàng)建成功之后,我們無(wú)法再創(chuàng)建一個(gè)同類(lèi)型的其他對(duì)象,所有的操作都只能基于這個(gè)唯一實(shí)例。為了確保對(duì)象的唯一性,我們可以通過(guò)單例模式來(lái)實(shí)現(xiàn),這就是單例模式的動(dòng)機(jī)所在。

2單例模式概述

下面我們來(lái)模擬實(shí)現(xiàn)Windows任務(wù)管理器,假設(shè)任務(wù)管理器的類(lèi)名為T(mén)askManager,在TaskManager類(lèi)中包含了大量的成員方法,例如構(gòu)造函數(shù)TaskManager(),顯示進(jìn)程的方法displayProcesses(),顯示服務(wù)的方法displayServices()等,該類(lèi)的示意代碼如下:

[java]view plaincopy

classTaskManager

{

publicTaskManager()?{……}//初始化窗口

publicvoiddisplayProcesses()??{……}//顯示進(jìn)程

publicvoiddisplayServices()?{……}//顯示服務(wù)

……

}

為了實(shí)現(xiàn)Windows任務(wù)管理器的唯一性,我們通過(guò)如下三步來(lái)對(duì)該類(lèi)進(jìn)行重構(gòu):

(1)由于每次使用new關(guān)鍵字來(lái)實(shí)例化TaskManager類(lèi)時(shí)都將產(chǎn)生一個(gè)新對(duì)象,為了確保TaskManager實(shí)例的唯一性,我們需要禁止類(lèi)的外部直接使用new來(lái)創(chuàng)建對(duì)象,因此需要將TaskManager的構(gòu)造函數(shù)的可見(jiàn)性改為private,如下代碼所示:

[java]view plaincopy

privateTaskManager()?{……}

(2)將構(gòu)造函數(shù)改為private修飾后該如何創(chuàng)建對(duì)象呢?不要著急,雖然類(lèi)的外部無(wú)法再使用new來(lái)創(chuàng)建對(duì)象,但是在TaskManager的內(nèi)部還是可以創(chuàng)建的,可見(jiàn)性只對(duì)類(lèi)外有效。因此,我們可以在TaskManager中創(chuàng)建并保存這個(gè)唯一實(shí)例。為了讓外界可以訪問(wèn)這個(gè)唯一實(shí)例,需要在TaskManager中定義一個(gè)靜態(tài)的TaskManager類(lèi)型的私有成員變量,如下代碼所示:

[java]view plaincopy

privatestaticTaskManager?tm?=null;

(3)為了保證成員變量的封裝性,我們將TaskManager類(lèi)型的tm對(duì)象的可見(jiàn)性設(shè)置為private,但外界該如何使用該成員變量并何時(shí)實(shí)例化該成員變量呢?答案是增加一個(gè)公有的靜態(tài)方法,如下代碼所示:

[java]view plaincopy

publicstaticTaskManager?getInstance()

{

if(tm?==null)

{

tm?=newTaskManager();

}

returntm;

}

在getInstance()方法中首先判斷tm對(duì)象是否存在,如果不存在(即tm == null),則使用new關(guān)鍵字創(chuàng)建一個(gè)新的TaskManager類(lèi)型的tm對(duì)象,再返回新創(chuàng)建的tm對(duì)象;否則直接返回已有的tm對(duì)象。

需要注意的是getInstance()方法的修飾符,首先它應(yīng)該是一個(gè)public方法,以便供外界其他對(duì)象使用,其次它使用了static關(guān)鍵字,即它是一個(gè)靜態(tài)方法,在類(lèi)外可以直接通過(guò)類(lèi)名來(lái)訪問(wèn),而無(wú)須創(chuàng)建TaskManager對(duì)象,事實(shí)上在類(lèi)外也無(wú)法創(chuàng)建TaskManager對(duì)象,因?yàn)闃?gòu)造函數(shù)是私有的。

思考

為什么要將成員變量tm定義為靜態(tài)變量?

通過(guò)以上三個(gè)步驟,我們完成了一個(gè)最簡(jiǎn)單的單例類(lèi)的設(shè)計(jì),其完整代碼如下:

[java]view plaincopy

classTaskManager

{

privatestaticTaskManager?tm?=null;

privateTaskManager()?{……}//初始化窗口

publicvoiddisplayProcesses()?{……}//顯示進(jìn)程

publicvoiddisplayServices()?{……}//顯示服務(wù)

publicstaticTaskManager?getInstance()

{

if(tm?==null)

{

tm?=newTaskManager();

}

returntm;

}

……

}

在類(lèi)外我們無(wú)法直接創(chuàng)建新的TaskManager對(duì)象,但可以通過(guò)代碼TaskManager.getInstance()來(lái)訪問(wèn)實(shí)例對(duì)象,第一次調(diào)用getInstance()方法時(shí)將創(chuàng)建唯一實(shí)例,再次調(diào)用時(shí)將返回第一次創(chuàng)建的實(shí)例,從而確保實(shí)例對(duì)象的唯一性。

上述代碼也是單例模式的一種最典型實(shí)現(xiàn)方式,有了以上基礎(chǔ),理解單例模式的定義和結(jié)構(gòu)就非常容易了。單例模式定義如下:

單例模式(Singleton Pattern):確保某一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類(lèi)稱(chēng)為單例類(lèi),它提供全局訪問(wèn)的方法。單例模式是一種對(duì)象創(chuàng)建型模式。

單例模式有三個(gè)要點(diǎn):一是某個(gè)類(lèi)只能有一個(gè)實(shí)例;二是它必須自行創(chuàng)建這個(gè)實(shí)例;三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。

單例模式是結(jié)構(gòu)最簡(jiǎn)單的設(shè)計(jì)模式一,在它的核心結(jié)構(gòu)中只包含一個(gè)被稱(chēng)為單例類(lèi)的特殊類(lèi)。單例模式結(jié)構(gòu)如圖3-2所示:

單例模式結(jié)構(gòu)圖中只包含一個(gè)單例角色:

●Singleton(單例):在單例類(lèi)的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例,同時(shí)它提供一個(gè)靜態(tài)的getInstance()工廠方法,讓客戶可以訪問(wèn)它的唯一實(shí)例;為了防止在外部對(duì)其實(shí)例化,將其構(gòu)造函數(shù)設(shè)計(jì)為私有;在單例類(lèi)內(nèi)部定義了一個(gè)Singleton類(lèi)型的靜態(tài)對(duì)象,作為外部共享的唯一實(shí)例。

3 負(fù)載均衡器的設(shè)計(jì)與實(shí)現(xiàn)

Sunny軟件公司承接了一個(gè)服務(wù)器負(fù)載均衡(Load Balance)軟件的開(kāi)發(fā)工作,該軟件運(yùn)行在一臺(tái)負(fù)載均衡服務(wù)器上,可以將并發(fā)訪問(wèn)和數(shù)據(jù)流量分發(fā)到服務(wù)器集群中的多臺(tái)設(shè)備上進(jìn)行并發(fā)處理,提高系統(tǒng)的整體處理能力,縮短響應(yīng)時(shí)間。由于集群中的服務(wù)器需要?jiǎng)討B(tài)刪減,且客戶端請(qǐng)求需要統(tǒng)一分發(fā),因此需要確保負(fù)載均衡器的唯一性,只能有一個(gè)負(fù)載均衡器來(lái)負(fù)責(zé)服務(wù)器的管理和請(qǐng)求的分發(fā),否則將會(huì)帶來(lái)服務(wù)器狀態(tài)的不一致以及請(qǐng)求分配沖突等問(wèn)題。如何確保負(fù)載均衡器的唯一性是該軟件成功的關(guān)鍵。

Sunny公司開(kāi)發(fā)人員通過(guò)分析和權(quán)衡,決定使用單例模式來(lái)設(shè)計(jì)該負(fù)載均衡器,結(jié)構(gòu)圖如圖3-3所示:

在圖3-3中,將負(fù)載均衡器LoadBalancer設(shè)計(jì)為單例類(lèi),其中包含一個(gè)存儲(chǔ)服務(wù)器信息的集合serverList,每次在serverList中隨機(jī)選擇一臺(tái)服務(wù)器來(lái)響應(yīng)客戶端的請(qǐng)求,實(shí)現(xiàn)代碼如下所示:

[java]view plaincopy

importjava.util.*;

//負(fù)載均衡器LoadBalancer:?jiǎn)卫?lèi),真實(shí)環(huán)境下該類(lèi)將非常復(fù)雜,包括大量初始化的工作和業(yè)務(wù)方法,考慮到代碼的可讀性和易理解性,只列出部分與模式相關(guān)的核心代碼

classLoadBalancer?{

//私有靜態(tài)成員變量,存儲(chǔ)唯一實(shí)例

privatestaticLoadBalancer?instance?=null;

//服務(wù)器集合

privateList?serverList?=null;

//私有構(gòu)造函數(shù)

privateLoadBalancer()?{

serverList?=newArrayList();

}

//公有靜態(tài)成員方法,返回唯一實(shí)例

publicstaticLoadBalancer?getLoadBalancer()?{

if(instance?==null)?{

instance?=newLoadBalancer();

}

returninstance;

}

//增加服務(wù)器

publicvoidaddServer(String?server)?{

serverList.add(server);

}

//刪除服務(wù)器

publicvoidremoveServer(String?server)?{

serverList.remove(server);

}

//使用Random類(lèi)隨機(jī)獲取服務(wù)器

publicString?getServer()?{

Random?random?=newRandom();

inti?=?random.nextInt(serverList.size());

return(String)serverList.get(i);

}

}

編寫(xiě)如下客戶端測(cè)試代碼:

[java]view plaincopy

classClient?{

publicstaticvoidmain(String?args[])?{

//創(chuàng)建四個(gè)LoadBalancer對(duì)象

LoadBalancer?balancer1,balancer2,balancer3,balancer4;

balancer1?=?LoadBalancer.getLoadBalancer();

balancer2?=?LoadBalancer.getLoadBalancer();

balancer3?=?LoadBalancer.getLoadBalancer();

balancer4?=?LoadBalancer.getLoadBalancer();

//判斷服務(wù)器負(fù)載均衡器是否相同

if(balancer1?==?balancer2?&&?balancer2?==?balancer3?&&?balancer3?==?balancer4)?{

System.out.println("服務(wù)器負(fù)載均衡器具有唯一性!");

}

//增加服務(wù)器

balancer1.addServer("Server?1");

balancer1.addServer("Server?2");

balancer1.addServer("Server?3");

balancer1.addServer("Server?4");

//模擬客戶端請(qǐng)求的分發(fā)

for(inti?=0;?i?<10;?i++)?{

String?server?=?balancer1.getServer();

System.out.println("分發(fā)請(qǐng)求至服務(wù)器:?"+?server);

}

}

}

編譯并運(yùn)行程序,輸出結(jié)果如下:

服務(wù)器負(fù)載均衡器具有唯一性!

分發(fā)請(qǐng)求至服務(wù)器:Server 1

分發(fā)請(qǐng)求至服務(wù)器:Server 3

分發(fā)請(qǐng)求至服務(wù)器:Server 4

分發(fā)請(qǐng)求至服務(wù)器:Server 2

分發(fā)請(qǐng)求至服務(wù)器:Server 3

分發(fā)請(qǐng)求至服務(wù)器:Server 2

分發(fā)請(qǐng)求至服務(wù)器:Server 3

分發(fā)請(qǐng)求至服務(wù)器:Server 4

分發(fā)請(qǐng)求至服務(wù)器:Server 4

分發(fā)請(qǐng)求至服務(wù)器:Server 1

雖然創(chuàng)建了四個(gè)LoadBalancer對(duì)象,但是它們實(shí)際上是同一個(gè)對(duì)象,因此,通過(guò)使用單例模式可以確保LoadBalancer對(duì)象的唯一性。

4 餓漢式單例與懶漢式單例的討論

Sunny公司開(kāi)發(fā)人員使用單例模式實(shí)現(xiàn)了負(fù)載均衡器的設(shè)計(jì),但是在實(shí)際使用中出現(xiàn)了一個(gè)非常嚴(yán)重的問(wèn)題,當(dāng)負(fù)載均衡器在啟動(dòng)過(guò)程中用戶再次啟動(dòng)該負(fù)載均衡器時(shí),系統(tǒng)無(wú)任何異常,但當(dāng)客戶端提交請(qǐng)求時(shí)出現(xiàn)請(qǐng)求分發(fā)失敗,通過(guò)仔細(xì)分析發(fā)現(xiàn)原來(lái)系統(tǒng)中還是存在多個(gè)負(fù)載均衡器對(duì)象,導(dǎo)致分發(fā)時(shí)目標(biāo)服務(wù)器不一致,從而產(chǎn)生沖突。為什么會(huì)這樣呢?Sunny公司開(kāi)發(fā)人員百思不得其解。

現(xiàn)在我們對(duì)負(fù)載均衡器的實(shí)現(xiàn)代碼進(jìn)行再次分析,當(dāng)?shù)谝淮握{(diào)用getLoadBalancer()方法創(chuàng)建并啟動(dòng)負(fù)載均衡器時(shí),instance對(duì)象為null值,因此系統(tǒng)將執(zhí)行代碼instance= new LoadBalancer(),在此過(guò)程中,由于要對(duì)LoadBalancer進(jìn)行大量初始化工作,需要一段時(shí)間來(lái)創(chuàng)建LoadBalancer對(duì)象。而在此時(shí),如果再一次調(diào)用getLoadBalancer()方法(通常發(fā)生在多線程環(huán)境中),由于instance尚未創(chuàng)建成功,仍為null值,判斷條件(instance== null)為真值,因此代碼instance= new LoadBalancer()將再次執(zhí)行,導(dǎo)致最終創(chuàng)建了多個(gè)instance對(duì)象,這違背了單例模式的初衷,也導(dǎo)致系統(tǒng)運(yùn)行發(fā)生錯(cuò)誤。

如何解決該問(wèn)題?我們至少有兩種解決方案,在正式介紹這兩種解決方案之前,先介紹一下單例類(lèi)的兩種不同實(shí)現(xiàn)方式,餓漢式單例類(lèi)和懶漢式單例類(lèi)。

1.餓漢式單例類(lèi)

餓漢式單例類(lèi)是實(shí)現(xiàn)起來(lái)最簡(jiǎn)單的單例類(lèi),餓漢式單例類(lèi)結(jié)構(gòu)圖如圖3-4所示:

從圖3-4中可以看出,由于在定義靜態(tài)變量的時(shí)候?qū)嵗瘑卫?lèi),因此在類(lèi)加載的時(shí)候就已經(jīng)創(chuàng)建了單例對(duì)象,代碼如下所示:

[java]view plaincopy

classEagerSingleton?{

privatestaticfinalEagerSingleton?instance?=newEagerSingleton();

privateEagerSingleton()?{?}

publicstaticEagerSingleton?getInstance()?{

returninstance;

}

}

當(dāng)類(lèi)被加載時(shí),靜態(tài)變量instance會(huì)被初始化,此時(shí)類(lèi)的私有構(gòu)造函數(shù)會(huì)被調(diào)用,單例類(lèi)的唯一實(shí)例將被創(chuàng)建。如果使用餓漢式單例來(lái)實(shí)現(xiàn)負(fù)載均衡器LoadBalancer類(lèi)的設(shè)計(jì),則不會(huì)出現(xiàn)創(chuàng)建多個(gè)單例對(duì)象的情況,可確保單例對(duì)象的唯一性。

5.懶漢式單例類(lèi)與線程鎖定

除了餓漢式單例,還有一種經(jīng)典的懶漢式單例,也就是前面的負(fù)載均衡器LoadBalancer類(lèi)的實(shí)現(xiàn)方式。懶漢式單例類(lèi)結(jié)構(gòu)圖如圖3-5所示:

從圖3-5中可以看出,懶漢式單例在第一次調(diào)用getInstance()方法時(shí)實(shí)例化,在類(lèi)加載時(shí)并不自行實(shí)例化,這種技術(shù)又稱(chēng)為延遲加載(Lazy Load)技術(shù),即需要的時(shí)候再加載實(shí)例,為了避免多個(gè)線程同時(shí)調(diào)用getInstance()方法,我們可以使用關(guān)鍵字synchronized,代碼如下所示:

[java]view plaincopy

classLazySingleton?{

privatestaticLazySingleton?instance?=null;

privateLazySingleton()?{?}

synchronizedpublicstaticLazySingleton?getInstance()?{

if(instance?==null)?{

instance?=newLazySingleton();

}

returninstance;

}

}

該懶漢式單例類(lèi)在getInstance()方法前面增加了關(guān)鍵字synchronized進(jìn)行線程鎖,以處理多個(gè)線程同時(shí)訪問(wèn)的問(wèn)題。但是,上述代碼雖然解決了線程安全問(wèn)題,但是每次調(diào)用getInstance()時(shí)都需要進(jìn)行線程鎖定判斷,在多線程高并發(fā)訪問(wèn)環(huán)境中,將會(huì)導(dǎo)致系統(tǒng)性能大大降低。如何既解決線程安全問(wèn)題又不影響系統(tǒng)性能呢?我們繼續(xù)對(duì)懶漢式單例進(jìn)行改進(jìn)。事實(shí)上,我們無(wú)須對(duì)整個(gè)getInstance()方法進(jìn)行鎖定,只需對(duì)其中的代碼“instance = new LazySingleton();”進(jìn)行鎖定即可。因此getInstance()方法可以進(jìn)行如下改進(jìn):

[java]view plaincopy

publicstaticLazySingleton?getInstance()?{

if(instance?==null)?{

synchronized(LazySingleton.class)?{

instance?=newLazySingleton();

}

}

returninstance;

}

問(wèn)題貌似得以解決,事實(shí)并非如此。如果使用以上代碼來(lái)實(shí)現(xiàn)單例,還是會(huì)存在單例對(duì)象不唯一。原因如下:

假如在某一瞬間線程A和線程B都在調(diào)用getInstance()方法,此時(shí)instance對(duì)象為null值,均能通過(guò)instance == null的判斷。由于實(shí)現(xiàn)了synchronized加鎖機(jī)制,線程A進(jìn)入synchronized鎖定的代碼中執(zhí)行實(shí)例創(chuàng)建代碼,線程B處于排隊(duì)等待狀態(tài),必須等待線程A執(zhí)行完畢后才可以進(jìn)入synchronized鎖定代碼。但當(dāng)A執(zhí)行完畢時(shí),線程B并不知道實(shí)例已經(jīng)創(chuàng)建,將繼續(xù)創(chuàng)建新的實(shí)例,導(dǎo)致產(chǎn)生多個(gè)單例對(duì)象,違背單例模式的設(shè)計(jì)思想,因此需要進(jìn)行進(jìn)一步改進(jìn),在synchronized中再進(jìn)行一次(instance == null)判斷,這種方式稱(chēng)為雙重檢查鎖定(Double-Check Locking)。使用雙重檢查鎖定實(shí)現(xiàn)的懶漢式單例類(lèi)完整代碼如下所示:

[java]view plaincopy

classLazySingleton?{

privatevolatilestaticLazySingleton?instance?=null;

privateLazySingleton()?{?}

publicstaticLazySingleton?getInstance()?{

//第一重判斷

if(instance?==null)?{

//鎖定代碼塊

synchronized(LazySingleton.class)?{

//第二重判斷

if(instance?==null)?{

instance?=newLazySingleton();//創(chuàng)建單例實(shí)例

}

}

}

returninstance;

}

}

需要注意的是,如果使用雙重檢查鎖定來(lái)實(shí)現(xiàn)懶漢式單例類(lèi),需要在靜態(tài)成員變量instance之前增加修飾符volatile,被volatile修飾的成員變量可以確保多個(gè)線程都能夠正確處理,且該代碼只能在JDK 1.5及以上版本中才能正確執(zhí)行。由于volatile關(guān)鍵字會(huì)屏蔽Java虛擬機(jī)所做的一些代碼優(yōu)化,可能會(huì)導(dǎo)致系統(tǒng)運(yùn)行效率降低,因此即使使用雙重檢查鎖定來(lái)實(shí)現(xiàn)單例模式也不是一種完美的實(shí)現(xiàn)方式。

擴(kuò)展

IBM公司高級(jí)軟件工程師Peter??? Haggar 2004年在IBM developerWorks上發(fā)表了一篇名為《雙重檢查鎖定及單例模式——全面理解這一失效的編程習(xí)語(yǔ)》的文章,對(duì)JDK??? 1.5之前的雙重檢查鎖定及單例模式進(jìn)行了全面分析和闡述,參考鏈接:http://www.ibm.com/developerworks/cn/java/j-dcl.html

6.餓漢式單例類(lèi)與懶漢式單例類(lèi)比較

餓漢式單例類(lèi)在類(lèi)被加載時(shí)就將自己實(shí)例化,它的優(yōu)點(diǎn)在于無(wú)須考慮多線程訪問(wèn)問(wèn)題,可以確保實(shí)例的唯一性;從調(diào)用速度和反應(yīng)時(shí)間角度來(lái)講,由于單例對(duì)象一開(kāi)始就得以創(chuàng)建,因此要優(yōu)于懶漢式單例。但是無(wú)論系統(tǒng)在運(yùn)行時(shí)是否需要使用該單例對(duì)象,由于在類(lèi)加載時(shí)該對(duì)象就需要?jiǎng)?chuàng)建,因此從資源利用效率角度來(lái)講,餓漢式單例不及懶漢式單例,而且在系統(tǒng)加載時(shí)由于需要?jiǎng)?chuàng)建餓漢式單例對(duì)象,加載時(shí)間可能會(huì)比較長(zhǎng)。

懶漢式單例類(lèi)在第一次使用時(shí)創(chuàng)建,無(wú)須一直占用系統(tǒng)資源,實(shí)現(xiàn)了延遲加載,但是必須處理好多個(gè)線程同時(shí)訪問(wèn)的問(wèn)題,特別是當(dāng)單例類(lèi)作為資源控制器,在實(shí)例化時(shí)必然涉及資源初始化,而資源初始化很有可能耗費(fèi)大量時(shí)間,這意味著出現(xiàn)多線程同時(shí)首次引用此類(lèi)的機(jī)率變得較大,需要通過(guò)雙重檢查鎖定等機(jī)制進(jìn)行控制,這將導(dǎo)致系統(tǒng)性能受到一定影響。

7 一種更好的單例實(shí)現(xiàn)方法

餓漢式單例類(lèi)不能實(shí)現(xiàn)延遲加載,不管將來(lái)用不用始終占據(jù)內(nèi)存;懶漢式單例類(lèi)線程安全控制煩瑣,而且性能受影響。可見(jiàn),無(wú)論是餓漢式單例還是懶漢式單例都存在這樣那樣的問(wèn)題,有沒(méi)有一種方法,能夠?qū)煞N單例的缺點(diǎn)都克服,而將兩者的優(yōu)點(diǎn)合二為一呢?答案是:Yes!下面我們來(lái)學(xué)習(xí)這種更好的被稱(chēng)之為Initialization Demand Holder (IoDH)的技術(shù)。

在IoDH中,我們?cè)趩卫?lèi)中增加一個(gè)靜態(tài)(static)內(nèi)部類(lèi),在該內(nèi)部類(lèi)中創(chuàng)建單例對(duì)象,再將該單例對(duì)象通過(guò)getInstance()方法返回給外部使用,實(shí)現(xiàn)代碼如下所示:

[java]view plaincopy

//Initialization?on?Demand?Holder

classSingleton?{

privateSingleton()?{

}

privatestaticclassHolderClass?{

privatefinalstaticSingleton?instance?=newSingleton();

}

publicstaticSingleton?getInstance()?{

returnHolderClass.instance;

}

publicstaticvoidmain(String?args[])?{

Singleton?s1,?s2;

s1?=?Singleton.getInstance();

s2?=?Singleton.getInstance();

System.out.println(s1==s2);

}

}

編譯并運(yùn)行上述代碼,運(yùn)行結(jié)果為:true,即創(chuàng)建的單例對(duì)象s1和s2為同一對(duì)象。由于靜態(tài)單例對(duì)象沒(méi)有作為Singleton的成員變量直接實(shí)例化,因此類(lèi)加載時(shí)不會(huì)實(shí)例化Singleton,第一次調(diào)用getInstance()時(shí)將加載內(nèi)部類(lèi)HolderClass,在該內(nèi)部類(lèi)中定義了一個(gè)static類(lèi)型的變量instance,此時(shí)會(huì)首先初始化這個(gè)成員變量,由Java虛擬機(jī)來(lái)保證其線程安全性,確保該成員變量只能初始化一次。由于getInstance()方法沒(méi)有任何線程鎖定,因此其性能不會(huì)造成任何影響。

通過(guò)使用IoDH,我們既可以實(shí)現(xiàn)延遲加載,又可以保證線程安全,不影響系統(tǒng)性能,不失為一種最好的Java語(yǔ)言單例模式實(shí)現(xiàn)方式(其缺點(diǎn)是與編程語(yǔ)言本身的特性相關(guān),很多面向?qū)ο笳Z(yǔ)言不支持IoDH)。

8 單例模式總結(jié)

單例模式作為一種目標(biāo)明確、結(jié)構(gòu)簡(jiǎn)單、理解容易的設(shè)計(jì)模式,在軟件開(kāi)發(fā)中使用頻率相當(dāng)高,在很多應(yīng)用軟件和框架中都得以廣泛應(yīng)用。

1.主要優(yōu)點(diǎn)

單例模式的主要優(yōu)點(diǎn)如下:

(1)單例模式提供了對(duì)唯一實(shí)例的受控訪問(wèn)。因?yàn)閱卫?lèi)封裝了它的唯一實(shí)例,所以它可以嚴(yán)格控制客戶怎樣以及何時(shí)訪問(wèn)它。

(2)由于在系統(tǒng)內(nèi)存中只存在一個(gè)對(duì)象,因此可以節(jié)約系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建和銷(xiāo)毀的對(duì)象單例模式無(wú)疑可以提高系統(tǒng)的性能。

(3)允許可變數(shù)目的實(shí)例?;趩卫J轿覀兛梢赃M(jìn)行擴(kuò)展,使用與單例控制相似的方法來(lái)獲得指定個(gè)數(shù)的對(duì)象實(shí)例,既節(jié)省系統(tǒng)資源,又解決了單例單例對(duì)象共享過(guò)多有損性能的問(wèn)題。

2.主要缺點(diǎn)

單例模式的主要缺點(diǎn)如下:

(1)由于單例模式中沒(méi)有抽象層,因此單例類(lèi)的擴(kuò)展有很大的困難。

(2)單例類(lèi)的職責(zé)過(guò)重,在一定程度上違背了“單一職責(zé)原則”。因?yàn)閱卫?lèi)既充當(dāng)了工廠角色,提供了工廠方法,同時(shí)又充當(dāng)了產(chǎn)品角色,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起。

(3)現(xiàn)在很多面向?qū)ο笳Z(yǔ)言(如Java、C#)的運(yùn)行環(huán)境都提供了自動(dòng)垃圾回收的技術(shù),因此,如果實(shí)例化的共享對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為它是垃圾,會(huì)自動(dòng)銷(xiāo)毀并回收資源,下次利用時(shí)又將重新實(shí)例化,這將導(dǎo)致共享的單例對(duì)象狀態(tài)的丟失。

3.適用場(chǎng)景

在以下情況下可以考慮使用單例模式:

(1)系統(tǒng)只需要一個(gè)實(shí)例對(duì)象,如系統(tǒng)要求提供一個(gè)唯一的序列號(hào)生成器或資源管理器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對(duì)象。

(2)客戶調(diào)用類(lèi)的單個(gè)實(shí)例只允許使用一個(gè)公共訪問(wèn)點(diǎn),除了該公共訪問(wèn)點(diǎn),不能通過(guò)其他途徑訪問(wèn)該實(shí)例。

注:非原創(chuàng),不裝逼,不虛假,整合分享

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 本文主要參考 那些年,我們一起寫(xiě)過(guò)的“單例模式”。 何為單例模式? 顧名思義,單例模式就是保證一個(gè)類(lèi)僅有一個(gè)...
    tandeneck閱讀 2,630評(píng)論 1 8
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,653評(píng)論 19 139
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮?jiǎn)潔易懂,是項(xiàng)目中最...
    成熱了閱讀 4,545評(píng)論 4 34
  • 1 場(chǎng)景問(wèn)題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,977評(píng)論 12 68
  • 一.什么是單例模式 單例模式的定義:確保一個(gè)類(lèi)只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)他的全局訪問(wèn)點(diǎn)。單例模式是幾個(gè)設(shè)計(jì)模式中...
    Geeks_Liu閱讀 2,330評(píng)論 0 10

友情鏈接更多精彩內(nèi)容