設計模式 - 單例

 

五種實現html

1.簡單實現java

 

 

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4
 5    Singleton()
 6    {
 7    }

 8
 9    public static Singleton Instance
10    {
11        get
12        {
13            if (instance==null)
14            {
15                instance = new Singleton();
16            }

17            return instance;
18        }

19    }

20}
web

這種方式的實現對於線程來講並非安全的,由於在多線程的環境下有可能獲得Singleton類的多個實例。若是同時有兩個線程去判斷(instance == null),而且獲得的結果爲真,這時兩個線程都會建立類Singleton的實例,這樣就違背了Singleton模式的原則。實際上在上述代碼中,有可能在計算出表達式的值以前,對象實例已經被建立,可是內存模型並不能保證對象實例在第二個線程建立以前被發現。編程

 

該實現方式主要有兩個優勢:設計模式

l         因爲實例是在 Instance 屬性方法內部建立的,所以類可使用附加功能(例如,對子類進行實例化),即便它可能引入不想要的依賴性。安全

l         直到對象要求產生一個實例才執行實例化;這種方法稱爲「惰性實例化」。惰性實例化避免了在應用程序啓動時實例化沒必要要的 singleton服務器

2.安全的線程 
多線程

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }

 9
10    public static Singleton Instance
11    {
12        get
13        {
14            lock (padlock)
15            {
16                if (instance==null)
17                {
18                    instance = new Singleton();
19                }

20                return instance;
21            }

22        }

23    }

24}

25
26併發

 

這種方式的實現對於線程來講是安全的。咱們首先建立了一個進程輔助對象,線程在進入時先對輔助對象加鎖而後再檢測對象是否被建立,這樣能夠確保只有一個實例被建立,由於在同一個時刻加了鎖的那部分程序只有一個線程能夠進入。這種狀況下,對象實例由最早進入的那個線程建立,後來的線程在進入時(instence == null)爲假,不會再去建立對象實例了。可是這種實現方式增長了額外的開銷,損失了性能。負載均衡

3.雙重鎖定

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }

 9
10    public static Singleton Instance
11    {
12        get
13        {
14            if (instance==null)
15            {
16                lock (padlock)
17                {
18                    if (instance==null)
19                    {
20                        instance = new Singleton();
21                    }

22                }

23            }

24            return instance;
25        }

26    }

27}

28

這種實現方式對多線程來講是安全的,同時線程不是每次都加鎖,只有判斷對象實例沒有被建立時它才加鎖,有了咱們上面第一部分的裏面的分析,咱們知道,加鎖後還得再進行對象是否已被建立的判斷。它解決了線程併發問題,同時避免在每一個 Instance 屬性方法的調用中都出現獨佔鎖定。它還容許您將實例化延遲到第一次訪問對象時發生。實際上,應用程序不多須要這種類型的實現。大多數狀況下咱們會用靜態初始化。這種方式仍然有不少缺點:沒法實現延遲初始化。

4.靜態初始化

 1public sealed class Singleton
 2{
 3    static readonly Singleton instance=new Singleton();
 4
 5    static Singleton()
 6    {
 7    }

 8
 9    Singleton()
10    {
11    }

12
13    public static Singleton Instance
14    {
15        get
16        {
17            return instance;
18        }

19    }

20}

21

看到上面這段富有戲劇性的代碼,咱們可能會產生懷疑,這仍是Singleton模式嗎?在此實現中,將在第一次引用類的任何成員時建立實例。公共語言運行庫負責處理變量初始化。該類標記爲sealed 以阻止發生派生,而派生可能會增長實例。此外,變量標記爲 readonly,這意味着只能在靜態初始化期間(此處顯示的示例)或在類構造函數中分配變量。

該實現與前面的示例相似,不一樣之處在於它依賴公共語言運行庫來初始化變量。它仍然能夠用來解決 Singleton 模式試圖解決的兩個基本問題:全局訪問和實例化控制。公共靜態屬性爲訪問實例提供了一個全局訪問點。此外,因爲構造函數是私有的,所以不能在類自己之外實例化 Singleton 類;所以,變量引用的是能夠在系統中存在的惟一的實例。

因爲 Singleton 實例被私有靜態成員變量引用,所以在類首次被對 Instance 屬性的調用所引用以前,不會發生實例化。

這種方法惟一的潛在缺點是,您對實例化機制的控制權較少。在 Design Patterns 形式中,您可以在實例化以前使用非默認的構造函數或執行其餘任務。因爲在此解決方案中由 .NET Framework 負責執行初始化,所以您沒有這些選項。在大多數狀況下,靜態初始化是在 .NET 中實現 Singleton 的首選方法。

5.延遲初始化

 1public sealed class Singleton
 2{
 3    Singleton()
 4    {
 5    }

 6
 7    public static Singleton Instance
 8    {
 9        get
10        {
11            return Nested.instance;
12        }

13    }

14    
15    class Nested
16    {
17        static Nested()
18        {
19        }

20
21        internal static readonly Singleton instance = new Singleton();
22    }

23}

24

這裏,初始化工做有Nested類的一個靜態成員來完成,這樣就實現了延遲初始化,並具備不少的優點,是值得推薦的一種實

現方式。

實現要點

l        Singleton模式是限制而不是改進類的建立。

l         Singleton類中的實例構造器能夠設置爲Protected以容許子類派生。

l         Singleton模式通常不要支持Icloneable接口,由於這可能致使多個對象實例,與Singleton模式的初衷違背。

l         Singleton模式通常不要支持序列化,這也有可能致使多個對象實例,這也與Singleton模式的初衷違背。

l         Singleton只考慮了對象建立的管理,沒有考慮到銷燬的管理,就支持垃圾回收的平臺和對象的開銷來說,咱們通常不必對其銷燬進行特殊的管理。

l         理解和擴展Singleton模式的核心是「如何控制用戶使用new對一個類的構造器的任意調用」。

 

 

l         能夠很簡單的修改一個Singleton,使它有少數幾個實例,這樣作是容許的並且是有意義的

優勢

l         實例控制:Singleton 會阻止其餘對象實例化其本身的 Singleton 對象的副本,從而確保全部對象都訪問惟一實例

l         靈活性:由於類控制了實例化過程,因此類能夠更加靈活修改實例化過程

缺點

l         開銷:雖然數量不多,但若是每次對象請求引用時都要檢查是否存在類的實例,將仍然須要一些開銷。能夠經過使用靜態初始化解決此問題,上面的五種實現方式中已經說過了。

l          可能的開發混淆:使用 singleton 對象(尤爲在類庫中定義的對象)時,開發人員必須記住本身不能使用 new 關鍵字實例化對象。由於可能沒法訪問庫源代碼,所以應用程序開發人員可能會意外發現本身沒法直接實例化此類。

l         對象的生存期:Singleton 不能解決刪除單個對象的問題。在提供內存管理的語言中(例如基於 .NET Framework 的語言),只有 Singleton 類可以致使實例被取消分配,由於它包含對該實例的私有引用。在某些語言中(如 C++),其餘類能夠刪除 
對象實例,但這樣會致使 Singleton 類中出現懸浮引用。

適用性

l         當類只能有一個實例並且客戶能夠從一個衆所周知的訪問點訪問它時。

l         當這個惟一實例應該是經過子類化可擴展的,而且客戶應該無需更改代碼就能使用一個擴展的實例時。

應用場景

l         每臺計算機能夠有若干個打印機,但只能有一個Printer Spooler,避免兩個打印做業同時輸出到打印機。 
(摘自呂震宇的
C#設計模式(7)-Singleton Pattern

l         PC機中可能有幾個串口,但只能有一個COM1口的實例。

l         系統中只能有一個窗口管理器。

l         .NET Remoting中服務器激活對象中的Sigleton對象,確保全部的客戶程序的請求都只有一個實例來處理。

完整示例

這是一個簡單的計數器例子,四個線程同時進行計數。

 1using System; 
 2using System.Threading; 
 3 
 4namespace SigletonPattern.SigletonCounter 
 5
 6    /// <summary> 
 7    /// 功能:簡單計數器的單件模式 
 8    /// 編寫:Terrylee 
 9    /// 日期:2005年12月06日 
10    /// </summary>
 
11    public class CountSigleton 
12    
13        ///存儲惟一的實例 
14        static CountSigleton uniCounter = new CountSigleton();   
15    
16        ///存儲計數值 
17        private int totNum = 0;   
18    
19        private CountSigleton()  
20    
21        {  
22            ///線程延遲2000毫秒 
23            Thread.Sleep(2000); 
24        }
  
25    
26        static public CountSigleton Instance()  
27    
28        {  
29    
30            return uniCounter;  
31    
32        }
  
33         
34        ///計數加1 
35        public void Add() 
36        {  
37            totNum ++
38        }
   
39         
40        ///得到當前計數值 
41        public int GetCounter() 
42        {  
43            return totNum; 
44        }
  
45 
46    }
 
47}
 
48

 

 1using System;
 2using System.Threading;
 3using System.Text;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7    /// <summary>
 8    /// 功能:建立一個多線程計數的類
 9    /// 編寫:Terrylee
10    /// 日期:2005年12月06日
11    /// </summary>

12    public class CountMutilThread
13    {
14        public CountMutilThread()
15        {
16            
17        }

18
19        /// <summary>
20        /// 線程工做
21        /// </summary>

22        public static void DoSomeWork()
23        {
24            ///構造顯示字符串
25            string results = "";
26
27            ///建立一個Sigleton實例
28            CountSigleton MyCounter = CountSigleton.Instance();
29
30            ///循環調用四次
31            for(int i=1;i<5;i++)
32            {
33                ///開始計數
34                MyCounter.Add();
35                
36                results +="線程";
37                results += Thread.CurrentThread.Name.ToString() + "——〉";
38                results += "當前的計數:";
39                results += MyCounter.GetCounter().ToString();
40                results += "\n";
41
42                Console.WriteLine(results);
43                
44                ///清空顯示字符串
45                results = "";
46            }

47        }

48
49        public void StartMain()
50        {
51
52            Thread thread0 = Thread.CurrentThread; 
53   
54            thread0.Name = "Thread 0"
55   
56            Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 
57   
58            thread1.Name = "Thread 1"
59   
60            Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 
61   
62            thread2.Name = "Thread 2"
63   
64            Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 
65   
66            thread3.Name = "Thread 3"
67   
68            thread1.Start(); 
69   
70            thread2.Start(); 
71   
72            thread3.Start(); 
73            
74            ///線程0也只執行和其餘線程相同的工做
75            DoSomeWork(); 
76        }

77    }

78}

79

 

 1using System; 
 2using System.Text; 
 3using System.Threading; 
 4 
 5namespace SigletonPattern.SigletonCounter 
 6
 7    /// <summary> 
 8    /// 功能:實現多線程計數器的客戶端 
 9    /// 編寫:Terrylee 
10    /// 日期:2005年12月06日 
11    /// </summary>
 
12    public class CountClient 
13    
14        public static void Main(string[] args) 
15        
16       CountMutilThread cmt = new CountMutilThread(); 
17 
18            cmt.StartMain(); 
19 
20            Console.ReadLine(); 
21        }
 
22    }
 
23}
 
24

 

 

 

 

前言:

這是一篇我見過的講單例模式最完整的,也是講的最好的一篇博客文章。


3. 1 單例模式的動機

      對於一個軟件系統的某些類而言,咱們無須建立多個實例。舉個你們都熟知的例子——Windows任務管理器,如圖3-1所示,咱們能夠作一個這樣的嘗試,在Windows的「任務欄」的右鍵彈出菜單上屢次點擊「啓動任務管理器」,看可否打開多個任務管理器窗口?若是你的桌面出現多個任務管理器,我請你吃飯,微笑(注:電腦中毒或私自修改Windows內核者除外)。一般狀況下,不管咱們啓動任務管理多少次,Windows系統始終只能彈出一個任務管理器窗口,也就是說在一個Windows系統中,任務管理器存在惟一性。爲何要這樣設計呢?咱們能夠從如下兩個方面來分析:其一,若是能彈出多個窗口,且這些窗口的內容徹底一致,所有是重複對象,這勢必會浪費系統資源,任務管理器須要獲取系統運行時的諸多信息,這些信息的獲取須要消耗必定的系統資源,包括CPU資源及內存資源等,浪費是可恥的,並且根本沒有必要顯示多個內容徹底相同的窗口;其二,若是彈出的多個窗口內容不一致,問題就更加嚴重了,這意味着在某一瞬間系統資源使用狀況和進程、服務等信息存在多個狀態,例如任務管理器窗口A顯示「CPU使用率」爲10%,窗口B顯示「CPU使用率」爲15%,到底哪一個纔是真實的呢?這純屬「調戲」用戶,給用戶帶來誤解,更不可取。因而可知,確保Windows任務管理器在系統中有且僅有一個很是重要。

         圖3-1 Windows任務管理器

      回到實際開發中,咱們也常常遇到相似的狀況,爲了節約系統資源,有時須要確保系統中某個類只有惟一一個實例,當這個惟一實例建立成功以後,咱們沒法再建立一個同類型的其餘對象,全部的操做都只能基於這個惟一實例。爲了確保對象的惟一性,咱們能夠經過單例模式來實現,這就是單例模式的動機所在。

3. 2 單例模式概述

      下面咱們來模擬實現Windows任務管理器,假設任務管理器的類名爲TaskManager,在TaskManager類中包含了大量的成員方法,例如構造函數TaskManager(),顯示進程的方法displayProcesses(),顯示服務的方法displayServices()等,該類的示意代碼以下:

class TaskManager

{

     public TaskManager() {……} //初始化窗口

     public void displayProcesses()  {……} //顯示進程

     public void  displayServices() {……} //顯示服務

     ……

}

      爲了實現Windows任務管理器的惟一性,咱們經過以下三步來對上類進行重構:

      (1)因爲每次使用new關鍵字來實例化TaskManager類時都將產生一個新對象,爲了確保TaskManager實例的惟一性,咱們須要禁止類的外部直接使用new來建立對象,所以須要將TaskManager的構造函數的可見性改成private,以下代碼所示:

     private TaskManager() {……}

      (2)將構造函數改成private修飾後該如何建立對象呢?不要着急,雖然類的外部沒法再使用new來建立對象,可是在TaskManager的內部仍是能夠建立的,可見性只對類外有效。所以,咱們能夠在TaskManager中建立並保存這個惟一實例。爲了讓外界能夠訪問這個惟一實例,須要在TaskManager中定義一個靜態的TaskManager類型的私有成員變量,以下代碼所示:

     private static TaskManager tm = null;

       (3)爲了保證成員變量的封裝性,咱們將TaskManager類型的tm對象的可見性設置爲private,但外界該如何使用該成員變量並什麼時候實例化該成員變量呢?答案是增長一個公有的靜態方法,以下代碼所示:

public static TaskManager getInstance()

{

    if (tm == null)

    {

        tm = new TaskManager();

    }

    return tm;

}

      在getInstance()方法中首先判斷tm對象是否存在,若是不存在(即tm == null),則使用new關鍵字建立一個新的TaskManager類型的tm對象,再返回新建立的tm對象;不然直接返回已有的tm對象。

      須要注意的是getInstance()方法的修飾符,首先它應該是一個public方法,以便供外界其餘對象使用,其次它使用了static關鍵字,即它是一個靜態方法,在類外能夠直接經過類名來訪問,而無須建立TaskManager對象,事實上在類外也沒法建立TaskManager對象,由於構造函數是私有的。 

思考

爲何要將成員變量tm定義爲靜態變量?

       經過以上三個步驟,咱們完成了一個最簡單的單例類的設計,其完整代碼以下:

class TaskManager

{

     private static TaskManager tm = null;

     private TaskManager() {……} //初始化窗口

     public void  displayProcesses() {……} //顯示進程

     public void  displayServices() {……} //顯示服務

     public static TaskManager getInstance()

    {

        if (tm == null)

        {

            tm = new TaskManager();

        }

        return tm;

    }

    ……

}

      在類外咱們沒法直接建立新的TaskManager對象,但能夠經過代碼TaskManager.getInstance()來訪問實例對象,第一次調用getInstance()方法時將建立惟一實例,再次調用時將返回第一次建立的實例,從而確保實例對象的惟一性。

      上述代碼也是單例模式的一種最典型實現方式,有了以上基礎,理解單例模式的定義和結構就很是容易了。單例模式定義以下: 

單例模式(Singleton Pattern):確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式是一種對象建立型模式。

      單例模式有三個要點:一是某個類只能有一個實例;二是它必須自行建立這個實例;三是它必須自行向整個系統提供這個實例。

單例模式是結構最簡單的設計模式一,在它的核心結構中只包含一個被稱爲單例類的特殊類。單例模式結構如圖3-2所示:

      單例模式結構圖中只包含一個單例角色:

● Singleton(單例):在單例類的內部實現只生成一個實例,同時它提供一個靜態的getInstance()工廠方法,讓客戶能夠訪問它的惟一實例;爲了防止在外部對其實例化,將其構造函數設計爲私有;在單例類內部定義了一個Singleton類型的靜態對象,做爲外部共享的惟一實例。


3.3 負載均衡器的設計與實現

Sunny軟件公司承接了一個服務器負載均衡(Load Balance)軟件的開發工做,該軟件運行在一臺負載均衡服務器上,能夠將併發訪問和數據流量分發到服務器集羣中的多臺設備上進行併發處理,提升系統的總體處理能力,縮短響應時間。因爲集羣中的服務器須要動態刪減,且客戶端請求須要統一分發,所以須要確保負載均衡器的惟一性,只能有一個負載均衡器來負責服務器的管理和請求的分發,不然將會帶來服務器狀態的不一致以及請求分配衝突等問題。如何確保負載均衡器的惟一性是該軟件成功的關鍵。

      Sunny公司開發人員經過分析和權衡,決定使用單例模式來設計該負載均衡器,結構圖如圖3-3所示:

        在圖3-3中,將負載均衡器LoadBalancer設計爲單例類,其中包含一個存儲服務器信息的集合serverList,每次在serverList中隨機選擇一臺服務器來響應客戶端的請求,實現代碼以下所示:

import java.util.*;

 

//負載均衡器LoadBalancer:單例類,真實環境下該類將很是複雜,包括大量初始化的工做和業務方法,考慮到代碼的可讀性和易理解性,只列出部分與模式相關的核心代碼

class LoadBalancer

{

       //私有靜態成員變量,存儲惟一實例

       private  static LoadBalancer instance = null;

       //服務器集合

       private  List serverList = null;

      

       //私有構造函數

       private  LoadBalancer()

       {

              serverList  = new ArrayList();

       }

      

       //公有靜態成員方法,返回惟一實例

       public  static LoadBalancer getLoadBalancer()

       {

              if  (instance == null)

              {

                     instance  = new LoadBalancer();

              }

              return  instance;

       }

      

       //增長服務器

       public  void addServer(String server)

       {

              serverList.add(server);

       }

      

       //刪除服務器

       public  void removeServer(String server)

       {

              serverList.remove(server);

       }

      

       //使用Random類隨機獲取服務器

       public  String getServer()

       {

              Random  random = new Random();

              int  i = random.nextInt(serverList.size());

              return  (String)serverList.get(i);

       }

}

       咱們能夠編寫以下客戶端代碼對其進行測試:

class Client

{

       public  static void main(String args[])

       {

         //建立四個LoadBalancer對象

              LoadBalancer  balancer1,balancer2,balancer3,balancer4;

              balancer1  = LoadBalancer.getLoadBalancer();

              balancer2  = LoadBalancer.getLoadBalancer();

              balancer3  = LoadBalancer.getLoadBalancer();

              balancer4  = LoadBalancer.getLoadBalancer();

             

              //判斷服務器負載均衡器是否相同

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

              {

                     System.out.println("服務器負載均衡器具備惟一性!");

              }

             

              //增長服務器

              balancer1.addServer("Server  1");

              balancer1.addServer("Server  2");

              balancer1.addServer("Server  3");

              balancer1.addServer("Server  4");

             

              //模擬客戶端請求的分發

              for  (int i = 0; i < 10; i++)

           {

            String server =  balancer1.getServer();

                     System.out.println("分發請求至服務器: " + server);

       }

       }

}

       編譯並運行程序,輸出結果以下:

服務器負載均衡器具備惟一性!

分發請求至服務器:  Server 1

分發請求至服務器:  Server 3

分發請求至服務器:  Server 4

分發請求至服務器:  Server 2

分發請求至服務器:  Server 3

分發請求至服務器:  Server 2

分發請求至服務器:  Server 3

分發請求至服務器:  Server 4

分發請求至服務器:  Server 4

分發請求至服務器:  Server 1

       雖然建立了四個LoadBalancer對象,可是它們其實是同一個對象,所以,經過使用單例模式能夠確保LoadBalancer對象的惟一性。


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

      Sunny公司開發人員使用單例模式實現了負載均衡器的設計,可是在實際使用中出現了一個很是嚴重的問題,當負載均衡器在啓動過程當中用戶再次啓動該負載均衡器時,系統無任何異常,但當客戶端提交請求時出現請求分發失敗,經過仔細分析發現原來系統中仍是存在多個負載均衡器對象,致使分發時目標服務器不一致,從而產生衝突。爲何會這樣呢?Sunny公司開發人員百思不得其解。

      如今咱們對負載均衡器的實現代碼進行再次分析,當第一次調用getLoadBalancer()方法建立並啓動負載均衡器時,instance對象爲null值,所以系統將執行代碼instance= new LoadBalancer(),在此過程當中,因爲要對LoadBalancer進行大量初始化工做,須要一段時間來建立LoadBalancer對象。而在此時,若是再一次調用getLoadBalancer()方法(一般發生在多線程環境中),因爲instance還沒有建立成功,仍爲null值,判斷條件(instance== null)爲真值,所以代碼instance= new LoadBalancer()將再次執行,致使最終建立了多個instance對象,這違背了單例模式的初衷,也致使系統運行發生錯誤。

      如何解決該問題?咱們至少有兩種解決方案,在正式介紹這兩種解決方案以前,先介紹一下單例類的兩種不一樣實現方式,餓漢式單例類和懶漢式單例類:

1.餓漢式單例類

      餓漢式單例類是實現起來最簡單的單例類,餓漢式單例類結構圖如圖3-4所示:

       從圖3-4中能夠看出,因爲在定義靜態變量的時候實例化單例類,所以在類加載的時候就已經建立了單例對象,代碼以下所示:

public class EagerSingleton

{

private static final  EagerSingleton instance = new EagerSingleton();

private EagerSingleton() { }

public static EagerSingleton getInstance()

{

return instance;

}

}

      當類被加載時,靜態變量instance會被初始化,此時類的私有構造函數會被調用,單例類的惟一實例將被建立。若是使用餓漢式單例來實現負載均衡器LoadBalancer類的設計,則不會出現建立多個單例對象的狀況,可確保單例對象的惟一性。

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

      除了餓漢式單例,還有一種經典的懶漢式單例,也就是前面的負載均衡器LoadBalancer類的實現方式。懶漢式單例類結構圖如圖3-5所示:

 

    從圖3-5中能夠看出,懶漢式單例在第一次調用getInstance()方法時實例化,在類加載時並不自行實例化,這種技術又稱爲延遲加載(Lazy Load)技術,即須要的時候再加載實例,爲了不多個線程同時調用getInstance()方法,咱們可使用關鍵字synchronized,代碼以下所示:

public class LazySingleton

{

private static LazySingleton instance = null;

 

private LazySingleton() { }

 

synchronized public  static LazySingleton getInstance()

{

if (instance == null)

{

instance = new  LazySingleton();

        }

return instance;

}

}

    該懶漢式單例類在getInstance()方法前面增長了關鍵字synchronized進行線程鎖,以處理多個線程同時訪問的問題。可是,上述代碼雖然解決了線程安全問題,可是每次調用getInstance()時都須要進行線程鎖定判斷,在多線程高併發訪問環境中,將會致使系統性能大大下降。如何既解決線程安全問題又不影響系統性能呢?咱們繼續對懶漢式單例進行改進。事實上,咱們無須對整個getInstance()方法進行鎖定,只需對其中的代碼「instance = new LazySingleton();」進行鎖定便可。所以getInstance()方法能夠進行以下改進:

public static LazySingleton getInstance()

{

if (instance == null)

{

    synchronized  (LazySingleton.class)

{

instance = new LazySingleton();

            }

        }

return instance;

}

       問題貌似得以解決,事實並不是如此。若是使用以上代碼來實現單例,仍是會存在單例對象不惟一。緣由以下:

      假如在某一瞬間線程A和線程B都在調用getInstance()方法,此時instance對象爲null值,均能經過instance == null的判斷。因爲實現了synchronized加鎖機制,線程A進入synchronized鎖定的代碼中執行實例建立代碼,線程B處於排隊等待狀態,必須等待線程A執行完畢後才能夠進入synchronized鎖定代碼。但當A執行完畢時,線程B並不知道實例已經建立,將繼續建立新的實例,致使產生多個單例對象,違背單例模式的設計思想,所以須要進行進一步改進,在synchronized中再進行一次(instance == null)判斷,這種方式稱爲雙重檢查鎖定(Double-Check Locking)。使用雙重檢查鎖定實現的懶漢式單例類完整代碼以下所示:

public 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();

}

            }

        }

return instance;

}

}

       須要注意的是,若是使用雙重檢查鎖定來實現懶漢式單例類,須要在靜態成員變量instance以前增長修飾符volatile,被volatile修飾的成員變量能夠確保多個線程都可以正確處理,且該代碼只能在JDK 1.5及以上版本中才能正確執行。因爲volatile關鍵字會屏蔽Java虛擬機所作的一些代碼優化,可能會致使系統運行效率下降,所以即便使用雙重檢查鎖定來實現單例模式也不是一種完美的實現方式。 

擴展

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

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

      餓漢式單例類在類被加載時就將本身實例化,它的優勢在於無須考慮多線程訪問問題,能夠確保實例的惟一性;從調用速度和反應時間角度來說,因爲單例對象一開始就得以建立,所以要優於懶漢式單例。可是不管系統在運行時是否須要使用該單例對象,因爲在類加載時該對象就須要建立,所以從資源利用效率角度來說,餓漢式單例不及懶漢式單例,並且在系統加載時因爲須要建立餓漢式單例對象,加載時間可能會比較長。

      懶漢式單例類在第一次使用時建立,無須一直佔用系統資源,實現了延遲加載,可是必須處理好多個線程同時訪問的問題,特別是當單例類做爲資源控制器,在實例化時必然涉及資源初始化,而資源初始化頗有可能耗費大量時間,這意味着出現多線程同時首次引用此類的機率變得較大,須要經過雙重檢查鎖定等機制進行控制,這將致使系統性能受到必定影響。


3.5 一種更好的單例實現方法

       餓漢式單例類不能實現延遲加載,無論未來用不用始終佔據內存;懶漢式單例類線程安全控制煩瑣,並且性能受影響。可見,不管是餓漢式單例仍是懶漢式單例都存在這樣那樣的問題,有沒有一種方法,可以將兩種單例的缺點都克服,而將二者的優勢合二爲一呢?答案是:Yes!下面咱們來學習這種更好的被稱之爲Initialization on Demand Holder (IoDH)的技術。

      在IoDH中,咱們在單例類中增長一個靜態(static)內部類,在該內部類中建立單例對象,再將該單例對象經過getInstance()方法返回給外部使用,實現代碼以下所示:

//Initialization on Demand Holder

public 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);

       }

}

      編譯並運行上述代碼,運行結果爲:true,即建立的單例對象s1s2爲同一對象。因爲靜態單例對象沒有做爲Singleton的成員變量直接實例化,所以類加載時不會實例化Singleton,第一次調用getInstance()時將加載內部類HolderClass,在該內部類中定義了一個static類型的變量instance,此時會首先初始化這個成員變量,由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。因爲getInstance()方法沒有任何線程鎖定,所以其性能不會形成任何影響。

      經過使用IoDH,咱們既能夠實現延遲加載,又能夠保證線程安全,不影響系統性能,不失爲一種最好的Java語言單例模式實現方式(其缺點是與編程語言自己的特性相關,不少面嚮對象語言不支持IoDH)。

練習

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

      至此,三種單例類的實現方式咱們均已學習完畢,它們分別是餓漢式單例、懶漢式單例以及IoDH


3.6 單例模式總結

單例模式做爲一種目標明確、結構簡單、理解容易的設計模式,在軟件開發中使用頻率至關高,在不少應用軟件和框架中都得以普遍應用。

1.主要優勢

單例模式的主要優勢以下:

(1) 單例模式提供了對惟一實例的受控訪問。由於單例類封裝了它的惟一實例,因此它能夠嚴格控制客戶怎樣以及什麼時候訪問它。

(2) 因爲在系統內存中只存在一個對象,所以能夠節約系統資源,對於一些須要頻繁建立和銷燬的對象單例模式無疑能夠提升系統的性能。

(3) 容許可變數目的實例。基於單例模式咱們能夠進行擴展,使用與單例控制類似的方法來得到指定個數的對象實例,既節省系統資源,又解決了單例單例對象共享過多有損性能的問題。

2.主要缺點

單例模式的主要缺點以下:

(1) 因爲單例模式中沒有抽象層,所以單例類的擴展有很大的困難。

(2) 單例類的職責太重,在必定程度上違背了「單一職責原則」。由於單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的建立和產品的自己的功能融合到一塊兒。

(3) 如今不少面嚮對象語言(JavaC#)的運行環境都提供了自動垃圾回收的技術,所以,若是實例化的共享對象長時間不被利用,系統會認爲它是垃圾,會自動銷燬並回收資源,下次利用時又將從新實例化,這將致使共享的單例對象狀態的丟失。

3.適用場景

在如下狀況下能夠考慮使用單例模式:

(1) 系統只須要一個實例對象,如系統要求提供一個惟一的序列號生成器或資源管理器,或者須要考慮資源消耗太大而只容許建立一個對象。

(2) 客戶調用類的單個實例只容許使用一個公共訪問點,除了該公共訪問點,不能經過其餘途徑訪問該實例。

原文做者:  http://blog.csdn.net/lovelion
相關文章
相關標籤/搜索