問題:
在軟體系統中,經常有這樣一些特殊的類,必須保證它們在系統中只存在一個執行個體,並提只有一個全域訪問點,才能確保它們的邏輯正確性、以及良好的效率。
定義:
單例模式也叫也叫單件模式是一種對象建立模式, 保證一個類僅有一個執行個體,並提供一個該執行個體全域的訪問點。這個類稱為單例類。
意圖:
確保某一個類只有一個執行個體,而且自行執行個體化並向整個系統提供這個執行個體。
單例模式有三個要點:一是某個類只能有一個執行個體;二是它必須自行建立這個執行個體;三是它必須自行向整個系統提供這個執行個體.
參與者:
•單例類:
確保類只有一個執行個體,而且自行執行個體化並向整個系統提供這個執行個體。
UML圖:
執行個體說明:
諾基亞手機工廠
諾基亞手機工廠,可以生產多款手機,可是手機工廠現在僅有一個,這裡使用單例模式確保系統中僅有一個手機工廠,保證系統無論什麼時候生產手機生產什麼手機都使用此工程。
uml圖:
代碼:
/// <summary>
/// 手機介面
/// </summary>
public interface INokiaPhone
{
string GetPhoneName();
}
/// <summary>
/// N8手機具體類
/// </summary>
public class N8Phone : INokiaPhone
{
public string GetPhoneName()
{
return "我是N8";
}
}
/// <summary>
/// N9手機具體類
/// </summary>
public class N9Phone : INokiaPhone
{
public string GetPhoneName()
{
return "我是N9";
}
}
/// <summary>
/// 手機生產工廠類
/// </summary>
public class PhoneFactory
{
public INokiaPhone CreateNokiaPhone(string phoneName)
{
switch (phoneName)
{
case "N8":
return new N8Phone();
case "N9":
return new N9Phone();
default:
return null;
}
}
}
/// <summary>
/// 諾基亞手機工廠單例
/// </summary>
public class SingletonPhoneFactory
{
/// <summary>
/// 因為靜態變數的生命週期跟整個應用程式的生命週期是一樣的,所以可以定義一個私人的靜態全域變數instance來儲存該類的唯一執行個體
/// </summary>
static SingletonPhoneFactory instance;
/// <summary>
/// 手機工廠執行個體
/// </summary>
PhoneFactory phoneFactory = null;
/// <summary>
/// 單例類私人建構函式,用於初始化類
/// 該Singleton的建構函式必須是私人的,以保證客戶程式不會通過new()操作產生一個執行個體,達到實現單例的目的
/// </summary>
private SingletonPhoneFactory()
{
phoneFactory = new PhoneFactory();
}
/// <summary>
/// 供一個全域函數訪問獲得該執行個體,並且在該函數提供控制執行個體數量的功能
/// </summary>
/// <returns></returns>
public static SingletonPhoneFactory GetInstance()
{
if (instance == null)
{
instance = new SingletonPhoneFactory();
}
return instance;
}
/// <summary>
/// 類方法
/// </summary>
/// <param name="_phoneName"></param>
/// <returns></returns>
public INokiaPhone CreateNokiaPhone(string _phoneName)
{
return phoneFactory.CreateNokiaPhone(_phoneName);
}
}
/// <summary>
/// 用戶端
/// </summary>
static void SingletonTest()
{
SingletonPhoneFactory SingletonPhoneFactory = SingletonPhoneFactory.GetInstance();
var n8 = SingletonPhoneFactory.CreateNokiaPhone("N8");
}
安全執行緒問題:
在多線程模式下當由兩個線程同時調用的GetInstance()時一個線程進入 if 判斷語句後但還沒有執行個體化 Singleton 時,第二個線程到達,此時 singleton 還是為 null這樣的話,兩個線程均通過 if 語句的條件判斷,然後調用了new Singleton()這樣就無法保證它的唯一執行個體。
•使用線程鎖的辦法來解決多線程下單例模式的安全執行緒問題:
public class Singleton
{
private static Singleton instance;
private static object _lock = new object();
private Singleton()
{ }
public static Singleton GetInstance()
{
if (instance == null)
{
lock (_lock)
{
if (instance == null)
{
instance = new Singleton();
}
}
} return instance;
}
}
這段代碼是最佳化過的,有兩個if(instance==null)做雙重判斷其目的是為瞭解決由線程同步帶來的效能問題。
每一次有線程進入 GetInstance()時,均會執行鎖定操作來實現線程同步,這是非常耗費效能的,而如果我加上第一重 singleton == null 的話,那麼就只有在第一次,也就是 singleton ==null 成立時的情況下執行一次鎖定以實現線程同步,而以後的話,便只要直接返回 Singleton 執行個體就 OK 了而根本無需再進入 lock 語句塊了,這樣就可以解決由線程同步帶來的效能問題了。
•使用readonly靜態初始化解決多線程單例安全執行緒問題
public sealed class Singleton
{
private static readonly Singleton singleton = new Singleton();
private Singleton()
{
}
public static Singleton GetInstance()
{
return singleton;
}
}
代碼中private static readonly Singleton singleton = new Singleton(); 因為是readonly的.net內部機制會保證此執行個體之初始化一次,所以這裡無需顯示地編寫安全執行緒代碼 。
靜態類和單例模式區別
•單例可以繼承類,實現介面,而靜態類不能;
•單例可以被延遲初始化,靜態類一般在第一次載入是初始化;
•單例類可以被繼承,他的方法可以被覆寫;
•單例類可以被用於多態而無需強迫使用者只假定唯一的執行個體。例如:如果有兩個諾基亞手機工廠,兩個工廠從同一個介面繼承,每個工廠都是單例,通過返回介面實現多態。
•在靜態方法中(類不是靜態,方法是靜態)產生的對象,會隨著靜態方法執行完畢而釋放掉,而且執行類中的靜態方法時,不會執行個體化靜態方法所在的類。如果是用singleton, 產生的那一個唯一的執行個體,會一直在記憶體中,不會被GC清除。
應用情景:
•當類只能有一個執行個體而且客戶可以從一個眾所周知的訪問點訪問它時。
•當這個唯一執行個體應該是通過子類化可擴充的,並且客戶應該無需更改代碼就能使用一個擴充的執行個體時。