1.0 c#設計模式 --單例模式
前言
先簡單說一下我前期(不理解的時候),記住單例模式的訣竅,是之前公開課上老師講的。單例模式,雙 if+lock
爲什麼存在單例模式?
很多時候,一個對象我們需要使用多次。但是每次使用都要新建一次。如果實例化的時候非常耗時,那麼整體的系統就會相當緩慢。
如下代碼,普通類型:
/// /// 學習單例模式案例 /// public class DemoSingleton { public DemoSingleton() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } public void Test() { Console.WriteLine("DemoSingleton Test 輸出"); } } class Program { static void Main(string[] args) { //調用示例 for(var i=1;i<10;i++) { var demo = new DemoSingleton(); demo.Test(); } Console.ReadKey(); } }
執行後的結果如下圖:
根據上述執行結果我們可以看出,每次都要實例化一次類型(在我們的模擬中,實例化耗時是比較長的),所以比較浪費資源。我們應該如何改進這種方式呢?
比較簡單的就是,我們將實例化從for循環中拿出來,這樣就可以實例化一次,避免了浪費時間的問題。但是,如果調用存在於多個方法中呢?
所以,我們可以將實例化放到類中實現,代碼如下:
/// /// 學習單例模式案例 /// public class DemoSingleton { private static DemoSingleton demo; private DemoSingleton() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } public static DemoSingleton CreateInstance() { if (demo == null) { demo = new DemoSingleton(); } return demo; } public void Test() { Console.WriteLine("DemoSingleton Test 輸出"); } } static void Main(string[] args) { //調用示例 for (var i = 1; i < 10; i++) { var demo = DemoSingleton.CreateInstance(); demo.Test(); } Console.ReadKey(); }
如上所示,將實例化的過程放在類中,這樣不論在哪裏調用,都會使用的同一個類(爲了保證外部不能實例化,所以構造函數使用了私有的)。具體執行效果請看下圖:
如上圖所示,結果還是與我們想要的是一致的。但是,在程序開發過程中,通常我們會使用多線程等來處理問題,構建系統。那麼調用方法就會被改成如下情況:
static void Main(string[] args) { //調用示例 for (var i = 1; i < 10; i++) { new Action(() => { var demo = DemoSingleton.CreateInstance(); demo.Test(); }).BeginInvoke(null,null); } Console.ReadKey(); }
調用結果如下圖:
完全不是我們想要的,但是問題出在哪裏呢?
由於我們使用的是異步方法,所以一次進入IF條件的有很多。所以,解決上面的問題就是解決如何限制一次進入IF條件的有單個。比較簡單的方式,就是加一個鎖,防止一次進入條件的有多個。具體代碼如下:
/// /// 學習單例模式案例 /// public class DemoSingleton { private static DemoSingleton demo; private static readonly object _lock=new object(); private DemoSingleton() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } public static DemoSingleton CreateInstance() { if (demo == null) { lock (_lock) { demo = new DemoSingleton(); } } return demo; } public void Test() { Console.WriteLine("DemoSingleton Test 輸出"); } } static void Main(string[] args) { //調用示例 for (var i = 1; i < 10; i++) { new Action(() => { var demo = DemoSingleton.CreateInstance(); demo.Test(); }).BeginInvoke(null,null); } Console.ReadKey(); }
執行效果如下圖:
如上圖,不是我們想要的。這時候,有的人就說了,你的目的是讓進入if條件的只有一個,應該把lock放到if的外層,那我們來修改一下構造函數:
public static DemoSingleton CreateInstance() { lock(_lock) { if (demo == null) { demo = new DemoSingleton(); } } return demo; }
執行結果結果如下:
如上圖所示,執行結果是我們想要的了。此刻,不斷進行優化的我們十分開心。但是,仔細看看代碼,是不是還有優化的空間呢?我們能不能做的更好,讓我們更加開心呢?
那麼,我們關注一下下面的代碼,思考一下這個問題:
lock(_lock)
{
if (demo == null)
{
demo = new DemoSingleton();
}
}
我們爲了讓同時進入if條件的只有一個,所以添加了lock鎖。那麼,我們分析一下代碼:i=1時,直接進入if,生成新的實例,同時i=2,i=3...被lock鎖限制在外面進行等待,此刻,i=1執行結束。後面的依次進入if條件判斷,發現已經存在實例了,直接返回。這樣看來,是不是有些耗時呢,明明可以直接返回的,結果還在進行等待,所以我進行了以下的優化:
public static DemoSingleton CreateInstance() { if(demo==null) { lock (_lock) { if (demo == null) { demo = new DemoSingleton(); } } } return demo; }
如上代碼,我們就可以在demo已經實例化的時候,無需等待直接返回。
具體什麼時候使用?
單例模式的主要作用,是保證整個進程中對象只被實例化一次。
正常情況下,如果一個類型,實例化的時候,非常耗時,或者計算非常複雜。就可以考慮使用單例模式,但是要注意以下的缺點
缺點:一直佔有內存(普通實例化,使用的時候創建,用完之後被CLR自動回收)
所以,我們要根據優缺點視情況而定。而不是你一味的使用單例模式,如果電腦的內存不夠大,很可能導致系統更加緩慢,或者崩潰。
詳細代碼示例
/// /// 學習單例模式案例 /// public class DemoSingleton { private static DemoSingleton demo; private static readonly object _lock=new object(); private DemoSingleton() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } public static DemoSingleton CreateInstance() { if(demo==null) { lock (_lock) { if (demo == null) { demo = new DemoSingleton(); } } } return demo; } public void Test() { Console.WriteLine("DemoSingleton Test 輸出"); } }
其它幾種單例模式的示例
理解了上面的案例,下面的應該就更好理解了
public class DemoSingletonStatic { private static DemoSingletonStatic demo=new DemoSingletonStatic(); private DemoSingletonStatic() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } public static DemoSingletonStatic CreateInstance() { return demo; } } public class DemoSingletonStaticTWO { private static DemoSingletonStaticTWO demo; private DemoSingletonStaticTWO() { //模擬耗時較長的情況 Thread.Sleep(1000); Console.WriteLine($"我誕生啦!在{Thread.CurrentThread.ManagedThreadId}中創建"); } static DemoSingletonStaticTWO() { demo = new DemoSingletonStaticTWO(); } public static DemoSingletonStaticTWO CreateInstance() { return demo; }
}
遺留問題:爲什麼靜態類不作爲單例模式的經典案例呢?
這個問題我思考了挺久,也在網上找了很多的解釋,但是沒有一種可以說服我。 目前有一個說法,我覺得還是相比較不錯的:
1 .我們第一種經典例子,是在使用的時候創建,而靜態類是在項目啓動的時候就已經創建了(但是,後面的幾個靜態的例子,打破了這個。這個觀點只適用於第一個)
2.靜態類再使用的時候,方法不可以進行重寫之類的(這個也有個疑問,我看網上有的人把單例模式中的類型上加了sealed修飾符,完全把這個有點給扼殺掉了。所以目前我也不清楚這個問題)
這個問題,如果有看了這篇文章瞭解的,還希望分享一下,感謝指導!