前言
什么是單例模式?
單例模式,屬於創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)
上面是百度百科給出的解釋。
大家都知道,面向對象的思想就是我們可以把一個類實例很多次,每次實例出來的都是一個對象,意味着你可以創建很多個基於這個類的對象。
單例模式,說白了,就是這些對象本質都是同一個,整個程序中,不管在哪里用,使用的都是同一個實例對象。
如果我們創建了一個China類,我們可以一直new嗎?不可以,因為世界上只有一個China,所以我們使用的都是同一個China對象。
Version 1 - 非線程安全
最簡單的實現方式如上圖,創建一個私有的靜態對象和私有構造方法,然后在CreateInstance方法里,加一個判斷,如果為Null,就重新實例化一下,否則直接返回。
這種寫法從邏輯上是沒問題的,但是是否會出現這個if (china == null)判斷,同時執行,這樣就麻煩了。
所以這種寫法在單線程的程序是沒問題的,但是在多線程中,是可能會有問題的。
我們做個測試,測試代碼如下:
上面的代碼,就是創建10個線程,都執行CreatInstance方法,那么最終是輸出多少次Console.WriteLine("實例化對象")呢?
我們測試發現,這個輸出結果是不唯一的,有時候會輸出5次,有時候會輸出2次,但是一般都是超過1次,這個就說明對象被多次實例化了,這就違背了單例模式的原則。
Version 2 - 簡單的線程安全
既然出現問題,那么我們就需要做一下優化,優化之后的代碼如下:
對比看下,就是加了一個同步鎖,這樣就可以避免同時執行的情況,並且,我們在lock里加了一個Console.WriteLine("執行判斷"),觀察這行代碼執行多少次。
從結果來看,實例化對象只執行了一次,說明對象只被創建過一次,滿足了我們的需求,達到了預期的效果。
Version 3 - 雙if+lock實現
上面那種方式已經可以達到預期效果,但是我們注意到一個問題,執行判斷這行代碼被執行了10次,這顯示不符合我們的邏輯,既然已經實例化了,為什么每次還要執行判斷呢?是不是多此一舉?並且每次請求對象,都會進行lock操作,lock對性能是有一定影響的。
於是我們繼續優化,優化之后的代碼如下:
我們對比代碼可以看出,就是又加了一個if (china == null),這種雙if+lock的方式,是不是可以解決我們的問題呢?
我們執行一次,看看結果:
我們通過結果可以看到只執行了一次判斷,也只執行一次實例化對象,但是我們還可以繼續優化。
Version 4 - 靜態變量實現
話不多說,直接上代碼:
利用靜態變量去實現單例,非常簡單,但同時也是線程安全的,由CLR保證,在程序第一次使用該類之前被調用,而且只調用一次。
但是這種方式也有缺點,就是實例化過程是在程序初始化時就執行的,而不是在使用時才執行,就是說,不管你用不用,都已經實例化了。
Version 5 - 完全懶漢式實現
這種方法與上一種方法類似,只是多加了一個類,來解決上一個版本的缺點。
Version 6 - 使用Lazy特性
從.NET 4開始,可以使用Lazytype來實現完全懶漢式,代碼也變得更簡單,代碼如下:
整體總結
可能大家看完之后,選擇困難症又會犯了吧?
這里給大家總結一下,除了Version 1,其他幾種情況,均可以實現單例模式,一般情況下我們使用Vesion 2和Version 4比較多,雖然Version 2會浪費一定的資源,但是很容易理解,實際應用中,影響不會很大。