1.0 c#設計模式 --單例模式

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修飾符,完全把這個有點給扼殺掉了。所以目前我也不清楚這個問題)

這個問題,如果有看了這篇文章瞭解的,還希望分享一下,感謝指導!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章