日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

确保对象的唯一性——单例模式

發(fā)布時間:2024/2/28 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 确保对象的唯一性——单例模式 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文轉(zhuǎn)載自 :http://blog.csdn.net/lovelion/article/details/7420883


3.1 單例模式的動機

??????對于一個軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個實例。舉個大家都熟知的例子——Windows任務(wù)管理器,如圖3-1所示,我們可以做一個這樣的嘗試,在Windows的“任務(wù)欄”的右鍵彈出菜單上多次點擊“啟動任務(wù)管理器”,看能否打開多個任務(wù)管理器窗口?如果你的桌面出現(xiàn)多個任務(wù)管理器,我請你吃飯,(注:電腦中毒或私自修改Windows內(nèi)核者除外)。通常情況下,無論我們啟動任務(wù)管理多少次,Windows系統(tǒng)始終只能彈出一個任務(wù)管理器窗口,也就是說在一個Windows系統(tǒng)中,任務(wù)管理器存在唯一性。為什么要這樣設(shè)計呢?我們可以從以下兩個方面來分析:其一,如果能彈出多個窗口,且這些窗口的內(nèi)容完全一致,全部是重復對象,這勢必會浪費系統(tǒng)資源,任務(wù)管理器需要獲取系統(tǒng)運行時的諸多信息,這些信息的獲取需要消耗一定的系統(tǒng)資源,包括CPU資源及內(nèi)存資源等,浪費是可恥的,而且根本沒有必要顯示多個內(nèi)容完全相同的窗口;其二,如果彈出的多個窗口內(nèi)容不一致,問題就更加嚴重了,這意味著在某一瞬間系統(tǒng)資源使用情況和進程、服務(wù)等信息存在多個狀態(tài),例如任務(wù)管理器窗口A顯示“CPU使用率”為10%,窗口B顯示“CPU使用率”為15%,到底哪個才是真實的呢?這純屬“調(diào)戲”用戶,,給用戶帶來誤解,更不可取。由此可見,確保Windows任務(wù)管理器在系統(tǒng)中有且僅有一個非常重要。

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

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

?

3.2?單例模式概述

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

[java]?view plaincopy
  • class?TaskManager??
  • {??
  • ?????public?TaskManager()?{……}?//初始化窗口??
  • ?????public?void?displayProcesses()??{……}?//顯示進程??
  • ?????public?void??displayServices()?{……}?//顯示服務(wù)??
  • ?????……??
  • }??
  • ???????為了實現(xiàn)Windows任務(wù)管理器的唯一性,我們通過如下三步來對該類進行重構(gòu):

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

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

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

    [java]?view plaincopy
  • public?static?TaskManager?getInstance()??
  • {??
  • ????if?(tm?==?null)??
  • ????{??
  • ????????tm?=?new?TaskManager();??
  • ????}??
  • ????return?tm;??
  • }??
  • ?????? 在getInstance()方法中首先判斷tm對象是否存在,如果不存在(即tm == null),則使用new關(guān)鍵字創(chuàng)建一個新的TaskManager類型的tm對象,再返回新創(chuàng)建的tm對象;否則直接返回已有的tm對象。

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

    ?

    思考

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

    ???????通過以上三個步驟,我們完成了一個最簡單的單例類的設(shè)計,其完整代碼如下:

    [java]?view plaincopy
  • class?TaskManager??
  • {??
  • ?????private?static?TaskManager?tm?=?null;??
  • ?????private?TaskManager()?{……}?//初始化窗口??
  • ?????public?void??displayProcesses()?{……}?//顯示進程??
  • ?????public?void??displayServices()?{……}?//顯示服務(wù)??
  • ?????public?static?TaskManager?getInstance()??
  • ?????{??
  • ????????if?(tm?==?null)??
  • ????????{??
  • ????????????tm?=?new?TaskManager();??
  • ????????}??
  • ????????return?tm;??
  • ????}??
  • ???……??
  • }??
  • ???????在類外我們無法直接創(chuàng)建新的TaskManager對象,但可以通過代碼TaskManager.getInstance()來訪問實例對象,第一次調(diào)用getInstance()方法時將創(chuàng)建唯一實例,再次調(diào)用時將返回第一次創(chuàng)建的實例,從而確保實例對象的唯一性。

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

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

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

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

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

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

    3.3 負載均衡器的設(shè)計與實現(xiàn)

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

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

    在圖3-3中,將負載均衡器LoadBalancer設(shè)計為單例類,其中包含一個存儲服務(wù)器信息的集合serverList,每次在serverList中隨機選擇一臺服務(wù)器來響應客戶端的請求,實現(xiàn)代碼如下所示: [java]?view plaincopy
  • import?java.util.*;??
  • ??
  • //負載均衡器LoadBalancer:單例類,真實環(huán)境下該類將非常復雜,包括大量初始化的工作和業(yè)務(wù)方法,考慮到代碼的可讀性和易理解性,只列出部分與模式相關(guān)的核心代碼??
  • class?LoadBalancer?{??
  • ????//私有靜態(tài)成員變量,存儲唯一實例??
  • ????private?static?LoadBalancer?instance?=?null;??
  • ????//服務(wù)器集合??
  • ????private?List?serverList?=?null;??
  • ??????
  • ????//私有構(gòu)造函數(shù)??
  • ????private?LoadBalancer()?{??
  • ????????serverList?=?new?ArrayList();??
  • ????}??
  • ??????
  • ????//公有靜態(tài)成員方法,返回唯一實例??
  • ????public?static?LoadBalancer?getLoadBalancer()?{??
  • ????????if?(instance?==?null)?{??
  • ????????????instance?=?new?LoadBalancer();??
  • ????????}??
  • ????????return?instance;??
  • ????}??
  • ??????
  • ????//增加服務(wù)器??
  • ????public?void?addServer(String?server)?{??
  • ????????serverList.add(server);??
  • ????}??
  • ??????
  • ????//刪除服務(wù)器??
  • ????public?void?removeServer(String?server)?{??
  • ????????serverList.remove(server);??
  • ????}??
  • ??????
  • ????//使用Random類隨機獲取服務(wù)器??
  • ????public?String?getServer()?{??
  • ????????Random?random?=?new?Random();??
  • ????????int?i?=?random.nextInt(serverList.size());??
  • ????????return?(String)serverList.get(i);??
  • ????}??
  • }??
  • ??????編寫如下客戶端測試代碼: [java]?view plaincopy
  • class?Client?{??
  • ????public?static?void?main(String?args[])?{??
  • ????????//創(chuàng)建四個LoadBalancer對象??
  • ????????LoadBalancer?balancer1,balancer2,balancer3,balancer4;??
  • ????????balancer1?=?LoadBalancer.getLoadBalancer();??
  • ????????balancer2?=?LoadBalancer.getLoadBalancer();??
  • ????????balancer3?=?LoadBalancer.getLoadBalancer();??
  • ????????balancer4?=?LoadBalancer.getLoadBalancer();??
  • ??????????
  • ????????//判斷服務(wù)器負載均衡器是否相同??
  • ????????if?(balancer1?==?balancer2?&&?balancer2?==?balancer3?&&?balancer3?==?balancer4)?{??
  • ????????????System.out.println("服務(wù)器負載均衡器具有唯一性!");??
  • ????????}??
  • ??????????
  • ????????//增加服務(wù)器??
  • ????????balancer1.addServer("Server?1");??
  • ????????balancer1.addServer("Server?2");??
  • ????????balancer1.addServer("Server?3");??
  • ????????balancer1.addServer("Server?4");??
  • ??????????
  • ????????//模擬客戶端請求的分發(fā)??
  • ????????for?(int?i?=?0;?i?<?10;?i++)?{??
  • ????????????String?server?=?balancer1.getServer();??
  • ????????????System.out.println("分發(fā)請求至服務(wù)器:?"?+?server);??
  • ??????}??
  • ????}??
  • }??
  • ????????編譯并運行程序,輸出結(jié)果如下:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ?

    1.餓漢式單例類

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

    ??????從圖3-4中可以看出,由于在定義靜態(tài)變量的時候?qū)嵗瘑卫?#xff0c;因此在類加載的時候就已經(jīng)創(chuàng)建了單例對象,代碼如下所示: [java]?view plaincopy
  • class?EagerSingleton?{???
  • ????private?static?final?EagerSingleton?instance?=?new?EagerSingleton();???
  • ????private?EagerSingleton()?{?}???
  • ??
  • ????public?static?EagerSingleton?getInstance()?{??
  • ????????return?instance;???
  • ????}?????
  • }??
  • ??????當類被加載時,靜態(tài)變量instance會被初始化,此時類的私有構(gòu)造函數(shù)會被調(diào)用,單例類的唯一實例將被創(chuàng)建。如果使用餓漢式單例來實現(xiàn)負載均衡器LoadBalancer類的設(shè)計,則不會出現(xiàn)創(chuàng)建多個單例對象的情況,可確保單例對象的唯一性。 ?

    2.懶漢式單例類與線程鎖定

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

    ????從圖3-5中可以看出,懶漢式單例在第一次調(diào)用getInstance()方法時實例化,在類加載時并不自行實例化,這種技術(shù)又稱為延遲加載(Lazy Load)技術(shù),即需要的時候再加載實例,為了避免多個線程同時調(diào)用getInstance()方法,我們可以使用關(guān)鍵字synchronized,代碼如下所示: [java]?view plaincopy
  • class?LazySingleton?{???
  • ????private?static?LazySingleton?instance?=?null;???
  • ??
  • ????private?LazySingleton()?{?}???
  • ??
  • ????synchronized?public?static?LazySingleton?getInstance()?{???
  • ????????if?(instance?==?null)?{??
  • ????????????instance?=?new?LazySingleton();???
  • ????????}??
  • ????????return?instance;???
  • ????}??
  • }??
  • ???????該懶漢式單例類在getInstance()方法前面增加了關(guān)鍵字synchronized進行線程鎖,以處理多個線程同時訪問的問題。但是,上述代碼雖然解決了線程安全問題,但是每次調(diào)用getInstance()時都需要進行線程鎖定判斷,在多線程高并發(fā)訪問環(huán)境中,將會導致系統(tǒng)性能大大降低。如何既解決線程安全問題又不影響系統(tǒng)性能呢?我們繼續(xù)對懶漢式單例進行改進。事實上,我們無須對整個getInstance()方法進行鎖定,只需對其中的代碼“instance = new LazySingleton();”進行鎖定即可。因此getInstance()方法可以進行如下改進: [java]?view plaincopy
  • public?static?LazySingleton?getInstance()?{???
  • ????if?(instance?==?null)?{??
  • ????????synchronized?(LazySingleton.class)?{??
  • ????????????instance?=?new?LazySingleton();???
  • ????????}??
  • ????}??
  • ????return?instance;???
  • }??
  • ???????問題貌似得以解決,事實并非如此。如果使用以上代碼來實現(xiàn)單例,還是會存在單例對象不唯一。原因如下:

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

    [java]?view plaincopy
  • class?LazySingleton?{???
  • ????private?volatile?static?LazySingleton?instance?=?null;???
  • ??
  • ????private?LazySingleton()?{?}???
  • ??
  • ????public?static?LazySingleton?getInstance()?{???
  • ????????//第一重判斷??
  • ????????if?(instance?==?null)?{??
  • ????????????//鎖定代碼塊??
  • ????????????synchronized?(LazySingleton.class)?{??
  • ????????????????//第二重判斷??
  • ????????????????if?(instance?==?null)?{??
  • ????????????????????instance?=?new?LazySingleton();?//創(chuàng)建單例實例??
  • ????????????????}??
  • ????????????}??
  • ????????}??
  • ????????return?instance;???
  • ????}??
  • }??
  • ?????? 需要注意的是,如果使用雙重檢查鎖定來實現(xiàn)懶漢式單例類,需要在靜態(tài)成員變量instance之前增加修飾符volatile,被volatile修飾的成員變量可以確保多個線程都能夠正確處理,且該代碼只能在JDK 1.5及以上版本中才能正確執(zhí)行。由于volatile關(guān)鍵字會屏蔽Java虛擬機所做的一些代碼優(yōu)化,可能會導致系統(tǒng)運行效率降低,因此即使使用雙重檢查鎖定來實現(xiàn)單例模式也不是一種完美的實現(xiàn)方式。?

    ?

    擴展

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

    ?

    3.餓漢式單例類與懶漢式單例類比較

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

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

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

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

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

    [java]?view plaincopy
  • //Initialization?on?Demand?Holder??
  • class?Singleton?{??
  • ????private?Singleton()?{??
  • ????}??
  • ??????
  • ????private?static?class?HolderClass?{??
  • ????????????private?final?static?Singleton?instance?=?new?Singleton();??
  • ????}??
  • ??????
  • ????public?static?Singleton?getInstance()?{??
  • ????????return?HolderClass.instance;??
  • ????}??
  • ??????
  • ????public?static?void?main(String?args[])?{??
  • ????????Singleton?s1,?s2;???
  • ????????????s1?=?Singleton.getInstance();??
  • ????????s2?=?Singleton.getInstance();??
  • ????????System.out.println(s1==s2);??
  • ????}??
  • }??
  • ???????編譯并運行上述代碼,運行結(jié)果為:true,即創(chuàng)建的單例對象s1s2為同一對象。由于靜態(tài)單例對象沒有作為Singleton的成員變量直接實例化,因此類加載時不會實例化Singleton,第一次調(diào)用getInstance()時將加載內(nèi)部類HolderClass,在該內(nèi)部類中定義了一個static類型的變量instance,此時會首先初始化這個成員變量,由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。由于getInstance()方法沒有任何線程鎖定,因此其性能不會造成任何影響。

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

    ?

    練習

    分別使用餓漢式單例、帶雙重檢查鎖定機制的懶漢式單例以及IoDH技術(shù)實現(xiàn)負載均衡器LoadBalancer。

    ??????至此,三種單例類的實現(xiàn)方式我們均已學習完畢,它們分別是餓漢式單例、懶漢式單例以及IoDH

    3.6 單例模式總結(jié)

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

    ?

    1.主要優(yōu)點

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

    ?????? (1)?單例模式提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它。

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

    ?????? (3)?允許可變數(shù)目的實例。基于單例模式我們可以進行擴展,使用與單例控制相似的方法來獲得指定個數(shù)的對象實例,既節(jié)省系統(tǒng)資源,又解決了單例單例對象共享過多有損性能的問題。

    ?

    2.主要缺點

    ?????? 單例模式的主要缺點如下:

    ?????? (1)?由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。

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

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

    ?

    3.適用場景

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

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

    ?????? (2)?客戶調(diào)用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。

    ?

    思考

    如何對單例模式進行改造,使得系統(tǒng)中某個類的對象可以存在有限多個,例如兩例或三例?【注:改造之后的類可稱之為多例類?!?/span>



    總結(jié)

    以上是生活随笔為你收集整理的确保对象的唯一性——单例模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。