小看--單例設計模式
(一)單例設計描述
只要了解過設計模式的同學都會知道:單例設計模式,大家都知道單例設計模式是一種創建行的設計模式。既然是創建型,那麽先來講講,對象的創建的過程吧。
--靜態成員:靜態成員在程序加載的時候,就會加載進內存。
--實例成員:只有new的時候才有實例成員。1、為實例的數據字段分配內存,然後初始化對象的附加字段(類型指針和同步索引塊),最後調用類型的實例構造器來設置對象的初始化狀態。
單例模式:一般用在一個類的創建對象很消耗資源,消耗時間,並且系統要保證只有一個對象的時候。一句話,對象的創建並不是很耗時間,不要刻意去套用單例模式,單例模式必須是在單例的時候,才單例。
(二) 單例模式的演變
下面我們來模擬實際情況
public class Singleton { /// <summary> /// 對象會持有資源 /// </summary> private List<string> _connList=new List<string>() { "測試數據庫連接","測試數據庫連接2","測試數據庫連接3","測試數據庫連接4" }; public Singleton() {long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被構造一次",this.GetType().Name); } public void Show() { Console.WriteLine("調用了Show"); } }
這個類的創建需要耗費很多資源,裏面有個Show方法。
那麽接下來,實際中,我們可能有十個地方要用到這個類裏面的Show方法,我們的做法是這樣的
//那麽接下來,我們這裏要調用十次Show方法 for (var i = 0; i < 10; i++) { var singletonObj = new Singleton(); singletonObj.Show(); }
這裏每次調用一次,都需要耗費很多資源和時間,這裏可能有些同學就會說,那我把這個singletonObj=new Singleton()提取出來,放到最外面來。
那行,按照我們需要,我們把var singletonObj=new Singletone()放到外面,如下所示
貌似這樣就解決了我們的問題,但是各位你們想一想,我們一個系統是有多個人開發的,A這裏這樣做,B可能不知道這裏有這個聲明,那他可能就還是一樣去New Singleton,還是導致我們系統中,存在大量這個對象。
我們應該要如何解決這個問題呢? 如何保證這個對象在整個系統只被創建一次呢?
單例模式--單線程
從上面的問題,我們也可以看出因為誰都可以在New Singleton,所以導致了這個問題。那按照這個想法,那我們就想啦,那就把構造函數私有化唄,私有化完了之後,我們應該還要提供一個方法或者啥的,給外面調用(也只能是靜態的成員),構造函數私有化了,外面是不可以New了的
那就按照,剛剛的說法,我們來進行一次改進
public class Singleton { /// <summary> /// 對象會持有資源 /// </summary> private List<string> _connList=new List<string>() { "測試數據庫連接","測試數據庫連接2","測試數據庫連接3","測試數據庫連接4" }; private Singleton(){ long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被構造一次",this.GetType().Name); } public static Singleton CreateInstance(){ return new Singleton(); } public void Show() { Console.WriteLine("調用了Show"); } }
按照我們上面這個寫法,把構造函數私有化了,然後在靜態方法裏面New Singletone();
調用結果如下:
for (var i = 0; i < 10; i++) { var singletonObj = Singleton.CreateInstance(); singletonObj.Show(); } //寫進裏面去了,是為了模擬有十個不同的開發,再調用
那結果,還是沒有達到我們想要的,那現在問題就是,對象沒有重用,因為我們每次new,導致了對象沒有做到重用,那就讓對象進行重用唄。最簡單的方法,就是給一個靜態的字段(為啥靜態呢,因為我們那邊方法是靜態的),然後做一個判斷,如果對象為空,那麽我們就創建,如果不為空,就不用創建了,如下所示。
我們在原來的基礎上,做了如上圖的改進。結果如下,
我們想要的結果實現了,多次調用的時候,做了重用對象,只構造了一次。本來想著單例模式就這樣結束了,編程世界裏面,我們總是要考慮一下,多線程的情況(剛剛我們的情況是單線程的)
單例模式--多線程
這個是用Task.Run()開啟了也給多線程的異步調用
for (var i = 0; i < 10; i++){ Task.Run(()=>{ var singletonObj = Singleton.CreateInstance(); singletonObj.Show(); }); } Thread.Sleep(5000);
通過上面的結果,我們可以看出,我們的之前的做法,在多線程環境還是會調用多次。
有些同學就會說用lock鎖啦,行,我們就給他加上一把鎖。
public class Singleton { /// <summary> /// 對象會持有資源 /// </summary> private List<string> _connList=new List<string>() { "測試數據庫連接","測試數據庫連接2","測試數據庫連接3","測試數據庫連接4" }; private static Singleton singletonObj = null; private static readonly object singleTonObjLock = new object(); //加鎖,之後這裏為啥要用readonly,大家可以找 private Singleton(){ long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被構造一次",this.GetType().Name); Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}調用一次"); } public static Singleton CreateInstance() { lock (singleTonObjLock) //很多同學都說用lock this,this肯定是不行的,因為lock是lock引用的,如果這個this的引用改變了... { if (singletonObj == null) { singletonObj = new Singleton(); } } return singletonObj; } public void Show() { Console.WriteLine("調用了Show"); } }
看我們給他加完鎖的時候效果。
嗯,實現了我們想要的效果了,說明我們加鎖是有效果的。到了這個時候,大家可能覺得一個單例模式應該就快結束了,那麽我們再來看看這種情況。
單例模式--多線程(雙if+lock)
通過上面的介紹,我們理解了單例模式的演變過程,也對單例模式,多線程有了更加深刻的印象。
(三)單例模式其他實現
就像我們一開始說的那樣,單例模式,其實一個進程內,在多線程環境下,如何保證只有一個對象,這就是單例。也可以從這個定義看出,我們可以通過靜態的構造函數來實現一個單例模式。
靜態構造函數,是由CLR保證的,有且只會加載一次。
其他很多方法實現,都是利用static關鍵字的背後原因,在第一次使用類型之前被調用,且只會被調用一次。
(四)懶漢式,餓漢式
懶漢,就是說這個人很懶,需要用的時候,才構建。雙if+lock這種就屬於懶漢式。
餓漢:就是調用我這個類型,就會幫你創建好;管你用不用,我都會幫你創建;就是餓了嗎,我後面介紹的利用static關鍵字的就是屬於餓漢式;
(五)單例模式的使用場景
單例:必須單例才單例,反正沒必要。單例模式實現都有性能,損失,靜態方法。
單例:會把對象常駐內存,靜態的。
單例的使用,多個人操作可能會對你影響,因為都是對同一份引用進行修改。
一般用在數據庫連接,打印機,遠程服務調用,等等這些大對象身上。
謝謝你閱讀我的博客,如果有收獲,請點一個贊(推薦)
小看--單例設計模式