設計模式-單例模式


定義

百度文庫:單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。

標准定義:Singleton保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

UML圖

 

示例

(1.1)定義一個私有的靜態的全局變量

  public class Singleton
  {
      //定義一個私有的靜態的全局變量
      private static Singleton singleton;
      
  }

(1.2)設置私有構造函數

 public class Singleton
  {
      //定義一個私有的靜態的全局變量
      private static Singleton singleton;

      /// <summary>
      /// 設置私有構造函數(外部無法使用new創建實例)
      /// </summary>
      private Singleton() { }
       
  }

(1.3)設置一個全局訪問點,靜態方法供外部調用

 public class Singleton
 {
        //定義一個私有的靜態的全局變量
        private static Singleton singleton;

        /// <summary>
        /// 設置私有構造函數(外部無法使用new創建實例)
        /// </summary>
        private Singleton() { }

        /// <summary>
        /// 設置一個全局訪問點,靜態方法供外部調用
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            //判斷保證實例化一次
            if (singleton == null)
                singleton = new Singleton();
            return singleton;
        }
  }

(1.4)調用代碼

class Program
{
     static void Main(string[] args)
     {
         Singleton instanceA = Singleton.GetInstance();
         Singleton instanceB = Singleton.GetInstance();

         Console.WriteLine(instanceA.Equals(instanceB) ? "一個實例" : "不是一個實例");
         Console.ReadKey();
     }
}

  

結果顯示一個實例,那么一個初步的單例模式我們就體驗完畢了

 

優化與思考

剛剛我們屬於單線程,那有沒有這樣一種情況,有多個線程一起創建當前實例,一個線程剛剛進入判斷IF中,另一個也進來了,此時興許有實例化2次的情況,這樣在某種情況下雖然不會報錯,但是默認多次實例化卻偏離了我們單例的初衷,下面我們來大概模擬一下這種情況.

(2.1)修改創建-我們在實例化代碼位置隨意輸出內容判斷進入次數   

 public class Singleton
  {
      //定義一個私有的靜態的全局變量
      private static Singleton singleton;
      /// <summary>
      /// 設置私有構造函數(外部無法使用new創建實例)
      /// </summary>
      private Singleton() { }
      /// <summary>
      /// 設置一個全局訪問點,靜態方法供外部調用
      /// </summary>
      /// <returns></returns>
      public static Singleton GetInstance()
      {
          //判斷保證實例化一次            
          if (singleton == null)
          {
              singleton = new Singleton();
              Console.WriteLine(DateTime.Now.ToString());
          }
          return singleton;
      }
  }

這里我輸出了時間

 

(2.2)調用多線程這里我們用2個線程舉例

 class Program
  {
      static void Main(string[] args)
      {
          Singleton instance = null;
          Parallel.For(0, 2, (i) =>
          {
              instance = Singleton.GetInstance();
          });
            
          Console.ReadKey();
      }
  }

(2.3)結果居然出現了2次,也就是實例化2次

 

分析

為了避免這種情況,我們可以將線程鎖定。意味着鎖定之后每次只會有一個線程進入判斷,實例化對象。一定程度避免了多次實例化。

(3.1)增加只讀靜態對象("鎖"對象)

//只讀靜態對象
private static readonly object syncObject = new object();  

(3.2)實例化加入判斷

        /// <summary>
        /// 設置一個全局訪問點,靜態方法供外部調用
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            if (singleton == null)
            {
                lock (syncObject)
                {
                    //判斷保證實例化一次         

                    singleton = new Singleton();
                    Console.WriteLine(DateTime.Now.ToString());

                }
            }
            return singleton;
        }

(3.3)結果

???怎么回事,怎么又出現2次了。。#¥¥&&@@@)¥)(@*@

所以最終我們雙重鎖定來解決問題在lock內部再加一層判定

        /// <summary>
        /// 設置一個全局訪問點,靜態方法供外部調用
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            if (singleton == null)
            {
                lock (syncObject)
                {
                    //判斷保證實例化一次         
                    if (singleton == null)
                    {
                        singleton = new Singleton();
                        Console.WriteLine(DateTime.Now.ToString());
                    }

                }
            }
            return singleton;
        }

結果:

 

補充:

我們上邊的例子采用的都是lazy load也就是懶加載,用的時候加載,當然還有一種方式就是餓漢模式,就是類初始化的時候直接實例化實例

   /// <summary>
    /// 餓漢
    /// </summary>
    public sealed class SingletonHungry
    {
        private static readonly SingletonHungry singleton = new SingletonHungry();

        public static SingletonHungry GetInstance()
        {
            return singleton;
        }
    }

 

 

優點

(1)實例控制:單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例。
(2)靈活性:因為類控制了實例化過程,所以類可以靈活更改實例化過程。 

缺點

(1)開銷:雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
(2)可能的開發混淆:使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用 new關鍵字實例化對象。因為可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
(3)對象生存期:不能解決刪除單個對象的問題。在提供內存管理的語言中(例如基於.NET Framework的語言),只有單例類能夠導致實例被取消分配,因為它包含對該實例的私有引用。在某些語言中(如 C++),其他類可以刪除對象實例,但這樣會導致單例類中出現懸浮引用。
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM