單例模式



單例模式

一. 意圖

    對於某些類來說,我們其實只需要有一個實例化的對象。比如:註冊表,資源管理器,打印機驅動程序等等。

如果我們保證以上的類只有一個實例,並只提供一個統一的訪問點的話。系統中便可以統一管理這個對象。

以上類只有一個實例,同時也可以節約系統資源,保證對象信息的一致性。

      我們可以通過單例模式來確保對象的唯一性。

二. 定義

        確保某一個類只有一個實例,只提供一個全局訪問點,該類自行實例化並向整個系統提供這個實例,這個類就是單例類

       類圖:

      

三.單例模式實現的三個要點

      
  1. 要保證類只有一個實例,就要禁止類的外部直接使用new來創建對象。把單例類的構造函數的可見性要改爲private,只在類的內部使用工廠方法創建實例。
  2. 在單例類中定義一個私有的靜態的自己類型的成員變量。
  3. 在單例類中定義一個公開的靜態的實例化方法。在實例化方法中判斷成員變量 是否實例化。如果沒有實例化,則創建一個自身實例。如果已經創建,就直接返回成員變量

        代碼示例(java)

       

四.餓漢式單例與懶漢式單例   

       上述的代碼示例,在運行多線程的程序中,可能還是存在一些問題,不能保證對象的唯一性。

例如:有線程1和線程2,同時調用Singleton類的Instance方法。線程1中判斷_instance字段爲null,對_instance字段進行初始化操作。

       如果Singleton類的初始化信息量大,初始化時間較長。在線程1初始化_instance字段的過程中,假設線程2也調用了Singleton類的Instance方法。如果此時_instance字段還是爲null的話,線程2也會對_instance字段進行初始化操作。從而產生了兩個Singleton類的實例。Singleton類的實例唯一性無法保證。

       對於多線程的問題,我可以使用餓漢和懶漢單例模式來保證對象的唯一性。


解決方案:

     1.餓漢式單例

  • 類圖

          

  • 代碼示例(java)

          

  • 說明

               當Singleton類加載時,靜態變量_instance會被初始化,此時Singleton類的私有構造函數會被調用,單例類的唯一實例就被創 建了。這樣在任何線程調用getInstance()方法之前,Singleton類已經被創建,確保了線程安全。


        2.懶漢式單例

  • 類圖

            

  • 代碼示例(java)

           

  •  說明
    • 懶漢式單例只有在調用了Instance()方法後,纔會實例化對象。並不是類一加載的時候,就實例化單例對象。使用延遲加載技術。
    • 在定義靜態變量_instance的時,使用了修飾符volatilevolatile修飾符可確保成員變量_instance在多個線程之間信息同步。但是會降低系統運行的效率。
    • 使用關鍵字synchronized可以對代碼進行了鎖定,一個線程在實例化對象的時候,另一個線程就必須等待。可以防止多個線程同時實例化Singleton對象。保證了線程的安全,但是鎖定代碼降低了系統運行的效率。
    • 使用了雙重檢查鎖定,代碼示例對_instance是否爲null值,進行了兩次判斷。

      第一個_instance == null的判斷,是有性能上的好處的。因爲只有在第一次_instance == null的時候,纔會有對代碼進行鎖定的操作。_instance不爲null的時候,就直接返回了_instance對象了。這樣對代碼進行鎖定的操作只會進行一次了。

     如果不進行雙重判斷,還是可能將會產生多個單例對象,從而違背單例模式的設計思想。

     假設有線程A和線程B同時調用了Instance()方法,此時_instance爲null,線程A和線程B都能通過instance == null的第一次判斷。

     由於實現了synchronized加鎖機制,線程A和線程B不能同時執行synchronized鎖定的代碼。

     假設線程A先進入synchronized鎖定的代碼,實例化Singleton對象。而線程B則處於排隊等待狀態,它必須等待線程A執行完畢後,纔可以進入synchronized鎖定的代碼中。

     但當線程A實例化完畢後,此時線程B並不知道Singleton對象已經被線程A實例化完畢了。所以必須在synchronized鎖定的代碼中加上第二個_instance==null的判斷。不然線程B將繼續創建新的實例,從而導致產生兩個單例對象。


   3.餓漢式單例 vs 懶漢式單例

   餓漢式單例

   優點:

       餓漢式單例在類加載的時候就被實例化,無需考慮多線程的問題。代碼也不需要鎖定,性能上有一定的優勢。

   缺點:

       不管系統中是否要引用實例,餓漢式單例在類加載的時候都會創建對象。如果此時系統不需要引用該實例,這樣就會造成系統資源的浪費。 

   懶漢式單例

   優點:

       懶漢式單例實現了延遲加載,在類需要被使用時,纔會被實例化,不會一直佔用系統資源。

   缺點:

       要處理好多線程同時訪問的問題,就需要鎖定代碼,考慮多個線程的同步,這樣就會對系統的性能造成一定的影響。

五.總結

         單例模式是一種比較簡單的創建型模式,在某種程度上來說它是限制了而不是促進了類的創建。保證類只有一個實例,並只提供一個全局的訪問點。在很多應用軟件中都有廣泛的應用。

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