目錄
- 一、什么是單例模式
- 二、單例模式的應用場景
- 三、單例模式的優缺點
- 四、單例模式的實現
- 五、總結
-
一、什么是單例模式
單例模式是一種常用的軟件設計模式,其定義是單例對象的類只能允許一個實例存在。
許多時候整個系統只需要擁有一個的全局對象,這樣有利於我們協調系統整體的行為。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在復雜環境下的配置管理。
單例的實現主要是通過以下兩個步驟:
- 將該類的構造方法定義為私有方法,這樣其他處的代碼就無法通過調用該類的構造方法來實例化該類的對象,只有通過該類提供的靜態方法來得到該類的唯一實例;
- 在該類內提供一個靜態方法,當我們調用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創建該類的實例並將實例的引用賦予該類保持的引。
- 適用場景:
- .需要生成唯一序列的環境
- 需要頻繁實例化然后銷毀的對象。
三、單例模式的優缺點
優點:
-
在內存中只有一個對象,節省內存空間;
-
避免頻繁的創建銷毀對象,可以提高性能;
-
避免對共享資源的多重占用,簡化訪問;
-
為整個系統提供一個全局訪問點。
缺點:
-
不適用於變化頻繁的對象;
-
濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;
-
如果實例化的對象長時間不被利用,系統會認為該對象是垃圾而被回收,這可能會導致對象狀態的丟失;
四、單例模式的實現
1.餓漢式
// 餓漢式單例 public class Singleton1 { // 指向自己實例的私有靜態引用,主動創建 private static Singleton1 singleton1 = new Singleton1(); // 私有的構造方法 private Singleton1(){} // 以自己實例為返回值的靜態的公有方法,靜態工廠方法 public static Singleton1 getSingleton1(){ return singleton1; } }
我們知道,類加載的方式是按需加載,且加載一次。。因此,在上述單例類被加載時,就會實例化一個對象並交給自己的引用,供系統使用;而且,由於這個類在整個生命周期中只會被加載一次,因此只會創建一個實例,即能夠充分保證單例。
優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費
2.懶漢式
// 懶漢式單例 public class Singleton2 { // 指向自己實例的私有靜態引用 private static Singleton2 singleton2; // 私有的構造方法 private Singleton2(){} // 以自己實例為返回值的靜態的公有方法,靜態工廠方法 public static Singleton2 getSingleton2(){ // 被動創建,在真正需要使用時才去創建 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }
我們從懶漢式單例可以看到,單例實例被延遲加載,即只有在真正使用的時候才會實例化一個對象並交給自己的引用。
這種寫法起到了Lazy Loading的效果,但是只能在單線程下使用。如果在多線程下,一個線程進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。所以在多線程環境下不可使用這種方式。
3.雙重加鎖機制
public class Singleton
{
private static Singleton instance;
//程序運行時創建一個靜態只讀的進程輔助對象
private static readonly object syncRoot = new object();
private Singleton() { }
public static Singleton GetInstance()
{
//先判斷是否存在,不存在再加鎖處理
if (instance == null)
{
//在同一個時刻加了鎖的那部分程序只有一個線程可以進入
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
Double-Check概念對於多線程開發者來說不會陌生,如代碼中所示,我們進行了兩次if (singleton == null)檢查,這樣就可以保證線程安全了。這樣,實例化代碼只用執行一次,后面再次訪問時,判斷if (singleton == null),直接return實例化對象。
使用雙重檢測同步延遲加載去創建單例的做法是一個非常優秀的做法,其不但保證了單例,而且切實提高了程序運行效率
優點:線程安全;延遲加載;效率較高。
4.靜態初始化
//阻止發生派生,而派生可能會增加實例 public sealed class Singleton { //在第一次引用類的任何成員時創建實例,公共語言運行庫負責處理變量初始化 private static readonly Singleton instance=new Singleton(); private Singleton() { } public static Singleton GetInstance() { return instance; } }
在實際應用中,C#與公共語言運行庫也提供了一種“靜態初始化”方法,這種方法不需要開發人員顯式地編寫線程安全代碼,即可解決多線程環境下它是不安全的問題。
五、總結
-
盡量減少同步塊的作用域;
-
盡量使用細粒度的鎖