【總結】設計模式應用之單例模式

1、前言

單例模式的應用場景十分清晰,就是一句話,在整個的軟件運行週期內,對於某個類只能容許有零個或一個實例。單例模式應用十分普遍,好比咱們電腦上的任務管理器就是一個單例模式,不管開多少個任務管理器,你會發現只有一個窗口,這就是典型的單例模式的應用;還有,網站的訪問次數統計,若是不採用單例模式會很難統計;多線程的線程池的設計通常也是採用單例模式,這是因爲線程池要方便對池中的線程進行控制;Web應用的配置對象的讀取,通常也應用單例模式,這是因爲配置文件是共享的資源;HttpApplication 也是單位例的典型應用,若是你瞭解ASP.Net(IIS)的整個請求生命週期,就會發現其實HttpApplication也是單例模式,全部的HttpModule都共享一個HttpApplication實例。這樣的例子實在太多了,那麼比較一下就會發現這些例子的共同點:資源共享的時候,適合使用單例,避免多個對象競爭產生死鎖,雖然能夠不使用單例,可是對性能仍是或多或少有些影響的。html

2、單例模式的實現

一、單例模式的構造函數是私有的,爲了不外部使用new操做符進行實例化而違背這一設計的初衷;設計模式

二、單例模式必須有一個全局訪問點,用於獲取當前的實例;多線程

既然單例模式是這樣,那就寫一下代碼看看:函數

 1 public class SingleTon
 2 {
 3     private static SingleTon _singleTon;
 4     //私有構造,避免new操做符
 5     private SingleTon()
 6     {
 7 
 8     }
 9 
10     public static SingleTon GetSingleInstance()
11     {
12         //若是_singleTon是空的,那麼建立一個實例並返回
13         if (_singleTon == null)
14         {
15             _singleTon = new SingleTon();
16         }
17         return _singleTon;
18     }
19 }

下面是客戶端調用代碼:性能

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         SingleTon s1 = SingleTon.GetSingleInstance();
 6         SingleTon s3 = SingleTon.GetSingleInstance();
 7         Console.WriteLine(s1.Equals(s3));//返回True
 8         Console.ReadLine();
 9     }
10 }

運行結果返回True,這是很明顯的。由於SingleTon第一次調用該類的GetSingleInstance方法時,進入到了if語句塊並建立了實例,而再次調用GetSingleInstance方法的時候,已經存在實例了,就直接把_singleTon返回了,因此s1和s2指向的實際上是同一個實例。你覺得單例模式就這麼完事兒了?遠沒有這麼簡單,上面的情形只是在單線程狀況下,假設這樣一個場景,若是在多線程的應用程序中,剛好有兩個線程同時訪問該類的GetSingleInstance方法,當第一個線程進入的時候,此時的_singleTon仍是null,第二個線程進入的時機就是在第一個線程已經進入if語句塊可是尚未建立實例的時候,此時問題就來了,兩個線程進入了if語句那麼必然會建立兩個實例,很明顯這不符合單例模式的邏輯。那怎麼樣才能避免上面的問題呢?加鎖。網站

3、多線程下的單例模式

多線程中,使用單例模式須要處理的就是如何避免多個線程建立多個實例的問題。能夠採用鎖機制來進行控制,代碼以下:編碼

 1 public class SingleTon
 2 {
 3     private static SingleTon _singleTon;
 4     private static readonly SingleTon _single = new SingleTon();
 5     private static readonly object _lock = new object();
 6     //私有構造,避免new操做符
 7     private SingleTon()
 8     {
 9 
10     }
11 
12     public static SingleTon GetSingleInstance()
13     {
14         if (_singleTon == null)
15         {
16             lock (_lock) 17             {
18                 if (_singleTon == null)
19                 {
20                     _singleTon = new SingleTon();
21                 }
22             }
23         }
24         return _singleTon;
25     }
26 
27     public static SingleTon GetSingleTonInstance()
28     {
29         return _single;
30     }
31 }

細心的你可能會發現這段代碼和上面的代碼相比,除了加了鎖,還有一個變化,就是多了一層非空判斷。下面我闡述下爲何這樣作,加鎖很容易理解,就是避免多個線程同時進入if語句建立多個實例,試想一下若是不加第一層if非空判斷會發生什麼?假設有兩個線程A和B,A和B線程同時訪問GetSingleInstance方法,A先進入,因爲加了鎖,因此B必須等待A完成操做退出後才能夠進入,如今A已經成功訪問建立實例的方法,所以_singleTon此時不爲空,B線程能夠進入,發現_singleTon不爲空了,直接退出。以上是A和B第一次訪問,接着又來了兩個線程C和D,一樣是C先進入lock語句塊,發現_singleTon此時不爲空並退出,此時等待中的D能夠進入了,和C發生了一樣的遭遇。不知道你看出問題沒有,既然第一次已經成功建立實例了,後續就沒有必要再進入lock塊了,由於不論你進或者不進入,被建立的實例就在那裏。其實最外層的非空判斷加與不加都不會影響單例的建立,因此最外層的非空判斷核心做用就是提高性能,避免沒有實際意義的操做。在個人另外一篇博文[設計模式應用之策略模式]中涉及到主題管理,負責主題管理的類就是一個單例模式。spa

4、懶漢式和餓漢式單例

懶漢模式就是第三部分的示例代碼,在這裏先把餓漢式單例的實現代碼貼出來進行比較:線程

 1 public class SingleTon
 2 {
 3     private static readonly SingleTon _single = new SingleTon();
 4     //私有構造,避免new操做符
 5     private SingleTon()
 6     {
 7 
 8     }
 9 
10     public static SingleTon GetSingleTonInstance()
11     {
12         return _single;
13     }
14 }

餓漢式單例相對於懶漢式單例就簡單多了,它直接在類的內部new一個SingleTon放在那,若是須要的時候就返回這個實例;反過來看一下懶漢式單例,它是在須要的時候,再去new一個SingleTon而後再返回這個實例。讀到這也許你就會明白這兩個名稱的由來了,餓漢式單例是比較積極的,我知道你要來拿這個實例,我提早給你準備好,你來的時候,我就直接給你;而懶漢式則比較懶,不到最後我是不會建立實例的,你來的時候我現場建立一個再給你。設計

5、總結

單例模式是最爲經常使用的設計模式之一,應用場景很是地廣,因此使用的時候,須要注意一下幾點:

  • 單例模式和核心就是程序的整個運行週期內只有很少於一個實例;
  • 私有化構造函數並建立一個公開的靜態的全局訪問點;
  • 若是是在多線程下使用,應該注意加鎖,以及雙重非空校驗;
  • 理解懶漢式單例和餓漢式單例;

對於設計模式,不須要死記硬背,只要記住幾個關鍵的點,而後在實際的編碼中多進行應用,就能夠掌握。

 

做者:悠揚的牧笛

博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6251162.html

聲明:本博客原創文字只表明本人工做中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未受權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

相關文章
相關標籤/搜索