介紹單例模式之前我們先來介紹下什麼是設計模式,所謂設計模式簡單來說就是根據開發者先輩們的經驗而總結出的解決問題的方式,可以說是前人經驗和心血的體現。 有了設計模式之後,我們可以少走很多彎路,利用設計模式來輕鬆解決對應的問題。 話不多說,今天先來介紹最容易入門和掌握的設計模式——單例模式 單例模式:我 ...
介紹單例模式之前我們先來介紹下什麼是設計模式,所謂設計模式簡單來說就是根據開發者先輩們的經驗而總結出的解決問題的方式,可以說是前人經驗和心血的體現。
有了設計模式之後,我們可以少走很多彎路,利用設計模式來輕鬆解決對應的問題。
話不多說,今天先來介紹最容易入門和掌握的設計模式——單例模式
單例模式:我們知道,在oo語言中,例如c#, JAVA,我們想要創建對象只要通過關鍵字new 即可,那麼如果我們希望整個程式運行過程中某個類只有一個實例該怎麼實現?
這個時候單例模式就出現了,它的目的就是確保程式運行過程中某個類只能有一個實例,並且提供一個全局訪問點。
隨著開發語言的演變,開發環境以及需求的多樣性,單例模式也有了眾多的實現方式,接下來會簡單的介紹幾個。
1. 經典實現
通過將構造函數設置為私有,以及暴露出一個靜態方法,來實現單例模式。
public class SingletonPattern { private SingletonPattern(){} private static SingletonPattern uniqueInstance; public static SingletonPattern getInstance() { if (uniqueInstance == null) { uniqueInstance = new SingletonPattern(); } return uniqueInstance; } }
var test = SingletonPattern.getInstance(); var test2 = SingletonPattern.getInstance(); var test3 = SingletonPattern.getInstance(); Console.WriteLine(test == test2); Console.WriteLine(test == test3); Console.WriteLine(test2 == test3); var item1 = new NormalClass(); var item2 = new NormalClass(); Console.WriteLine(item1 == item2);
由上述代碼可見,通過靜態方法來返回唯一的實例,並且只有在初次調用這個靜態方法的時候才實例化(延遲實例化),這樣也就確保了再需要的時候實例化而不是在程式運行開始實例化。
那麼上述代碼是否已經達到我們的目的了呢?答案是不完全能夠,如果程式運行中確保不會有多個線程同時訪問這個靜態方法,那麼這個寫法已經足夠了,但是如果有呢?
public class SingletonPattern { private SingletonPattern(){} private static SingletonPattern uniqueInstance; public static SingletonPattern getInstance() { if (uniqueInstance == null) { Console.WriteLine("實例化");//標記實例化 uniqueInstance = new SingletonPattern(); } return uniqueInstance; } }
TaskFactory taskFactory = new TaskFactory(); for (var i = 0; i <= 5; i++) { taskFactory.StartNew(() => SingletonPattern.getInstance()); }
可以看到多線程下,該類被實例化了四次,那麼該如何解決多線程的問題呢?
2. c#中通過lock關鍵字來解決線程同步問題
只要在上述代碼中略微做些改動,就能夠解決多線程的隱患
public class SingletonPattern { private SingletonPattern(){} private static SingletonPattern uniqueInstance; private static object locker = new object();//用於作為lock的對象,一個標誌位,lock對象必須是引用類型 public static SingletonPattern getInstance() { lock (locker)//加鎖,locker對象,以及代碼塊中的代碼都不能被其他線程調用 { if (uniqueInstance == null) { Console.WriteLine("實例化");//標記實例化 uniqueInstance = new SingletonPattern(); } return uniqueInstance; } } }
加了鎖之後,我們確實解決了線程同步的問題,但是加鎖對性能有著影響,而且仔細觀察上述代碼,我們對每一個線程都進行了加鎖,是否有這個必要呢?
其實我們加鎖的目的就是為了確保只有一個線程來實例化了該類,一旦初始化之後,之後無論有幾個線程同時調用這個靜態方法,那麼它們都將獲得同一個對象。
3. 雙重鎖 if+lock
我們在上述代碼中,做如下改動,確保只有在第一次實例化的時候,對線程加鎖。
public class SingletonPattern { private SingletonPattern(){} private static SingletonPattern uniqueInstance; private static object locker = new object();//用於作為lock的對象,一個標誌位,lock對象必須是引用類型 public static SingletonPattern getInstance() { if (uniqueInstance == null)//判斷是否已經被實例化,如果沒有,往下走 { lock (locker)//因為沒有被實例化,所以加鎖,接著實例化 { if (uniqueInstance == null) { Console.WriteLine("實例化");//標記實例化 uniqueInstance = new SingletonPattern(); } } } return uniqueInstance; } }
上述代碼執行過程為先判斷是否已經實例化,沒有那麼加鎖創建實例,如果有則返回,一旦創建之後,就沒有必要在去加鎖。
這樣既保證了多線程安全,又沒有對性能造成過多的影響。
4. 除了上述幾種方式之外,還可以通過把實例賦值給靜態變數,那麼也能確保該實例唯一,但是這個實例會在程式運行的時候就被初始化,而不是按需初始化。
實現方式不再這邊描述,可以自行嘗試。