1. 程式人生 > >小看--單例設計模式

小看--單例設計模式

實例 ren one src 數據庫連接 new 多次調用 tel 構造器

(一)單例設計描述

只要了解過設計模式的同學都會知道:單例設計模式,大家都知道單例設計模式是一種創建行的設計模式。既然是創建型,那麽先來講講,對象的創建的過程吧。

--靜態成員:靜態成員在程序加載的時候,就會加載進內存。

--實例成員:只有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關鍵字的就是屬於餓漢式;

(五)單例模式的使用場景

單例:必須單例才單例,反正沒必要。單例模式實現都有性能,損失,靜態方法。

單例:會把對象常駐內存,靜態的。

單例的使用,多個人操作可能會對你影響,因為都是對同一份引用進行修改。

一般用在數據庫連接,打印機,遠程服務調用,等等這些大對象身上。

謝謝你閱讀我的博客,如果有收獲,請點一個贊(推薦)

小看--單例設計模式