單例模式的介紹
在軟件的開發過程中,很多時候,我們需要對一個類進行實例化后,再使用,有時這個類比較簡單,有時也可能會很復雜,但不管怎樣,為了保證軟件的質量和效率,大多數時候,我們只希望它被實例化一次,所以這就需要引入單例模式(Singleton Pattern)了。單例模式,即保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。那,怎么做到呢?為不不被實例化多個對象,就可以讓類自身負責保存它的唯一實例。軟件中重復使用某個類時,為了防止多次實例化產生的資源消耗,這個時候就應該使用單例設計模式了。如:網絡請求、數據庫操作等。

上面為單例的類圖
單例要點
- 構造函數不對外開放,一般為 private;
- 通過一個靜態方法或者枚舉返回單例對象;
- 確保單例類的對象有且只有一個,尤其是在多線程環境下;
- 確保單例類對象在反序列化時不會被重新構建對象。
代碼示例
簡單實現
public class Singleton
{
private static Singleton instance;
/// <summary>
/// 構造方法為private,可以僻免外界通過利用new創建此類實例的可能
/// </summary>
private Singleton() { }
/// <summary>
/// 此方法為獲得本類實例的唯一全局訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
if (instance == null)
{
return instance;
}
return instance;
}
}
上面的寫法,可以實現單例模式,在單線程的情況下,並沒有問題,但如果在多線程的環境下,兩個或更多的線程同時判斷 instance == null 為 true, 那么這個類仍然可以被多次實例化,那么它是不安全的,並不是真正的單例。這個時候,我們就會想到,可以在這之前,加上 lock 解決問題。提示代碼如下:
線程安全
public class Singleton
{
private static readonly object locker = new object();
private static Singleton instance;
/// <summary>
/// 構造方法為private,可以僻免外界通過利用new創建此類實例的可能
/// </summary>
//private Singleton() { }
/// <summary>
/// 此方法為獲得本類實例的唯一全局訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
lock (locker)
{
if (instance == null)
{
return instance;
}
}
return instance;
}
}
lock( locker) // 在同一時刻加了鎖的那部分程序,只有一個線程可以進入,其他線程要進入,只能待已進行的線程退出lock程序塊后,才能進入,這就保證了線程安全,不會創建多個實例。現在線程安全是解決了,但還是有個小問題,每次獲取實例的時候,都需要加鎖,然后才能判斷實例是否被創建了,怎么解決這個問題?這就需要再引入一個 雙重鎖 的做法了。
雙重鎖定
/// <summary>
///
/// </summary>
public class Singleton
{
private static readonly object locker = new object();
private static Singleton instance;
/// <summary>
/// 構造方法為private,可以僻免外界通過利用new創建此類實例的可能
/// </summary>
//private Singleton() { }
/// <summary>
/// 此方法為獲得本類實例的唯一全局訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 判斷1
if (instance == null)
{
lock (locker)
{
// 判斷2
if (instance == null)
{
return instance;
}
}
}
return instance;
}
}
上面的代碼,在lock(locker) 前后,都加入了 對實例是否創建的判斷, 判斷1 主要是考慮到在實例已經被創建后,僻免程序再鎖定 后面的程序塊,減少資源的浪費,判斷2 是為了 在沒有創建實例的情況下才創建實例。經過多次改造后的單例,看似完美,但還是在獲取實例的時候需要去判斷實例是不是被創建了,那么,有沒有別的方式每次都去判斷呢?答案是有的,C#與公共語言運行庫也提供了一種 "靜態初始化" 方法,這種方法不需要開發人員顯式地編寫線程安全代碼。
靜態初始化
/// <summary>
/// 這里用到了 sealed 主要是不讓其他類繼承,而繼承可能會增加實例
/// </summary>
public sealed class Singleton
{
//在第一次引用類的任何成員時創建實例,由公共語言運行庫負責處理變量的初始化
private static readonly Singleton instance = new Singleton();
//構造函數私有化,是為了不被通過 new 達到實例化的目的
private Singleton() { }
public static Singleton GetSingleton() {
return instance;
}
}
這個實現方式與前面的示例類似,都是解決了單例模式試圖解決的兩個基本問題:全局訪問和實例化控制,公共靜態屬性是為訪問實例提供了一個全局訪問點。不同的地方在於它依賴公共語言運行庫來初始化變量。再就是它的構造方法標記為私有,所以不能在類本身以外的地方通過 new 來實例化 Singleton 類;因此變量引用的是可以在系統中存在唯一的實例。值得注意的是,instance 變量標記為 readonly,也就是只能在靜態初始化期間 或 在類的構造函數中分配變量。關於 sealed 的關鍵字,也可以在上面幾個類中使用,只是類可能要稍加改動。
最后,感覺各位能花時間看到這里,謝謝,歡迎留言交流。
參考文獻:
大話設計模式 程傑 著
