為什麼將單例模式排名第一,很簡單,面試的時候聊到設計模式,大概率就從單例模式開始入手,循循漸進。 當然,我們學習單例模式不是為了去應付面試,而是學好設計模式後,融匯貫通,應用於我們的設計,開發,項目中。 單例模式是最簡單的設計模式之一 單例模式【Singleton Pattern】:保證一個類僅有一 ...
為什麼將單例模式排名第一,很簡單,面試的時候聊到設計模式,大概率就從單例模式開始入手,循循漸進。
當然,我們學習單例模式不是為了去應付面試,而是學好設計模式後,融匯貫通,應用於我們的設計,開發,項目中。
單例模式是最簡單的設計模式之一
單例模式【Singleton Pattern】:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
兩層含義:1,有且僅有一個實例
2,有一個訪問他的全局訪問點
我們來想象一個場景,放眼全球,所有的黨派,都只有一個主席(這應該沒有例外吧)這個案例,如果要拜訪他,是不是需要一個全局的訪問點。
對於一個類來說,我們怎樣保證他僅有一個實例,初步想到的是不准外部實例化,因為如果可以外部實例化,我就可以實例化無數個。那不准外部實例化,就只能內部了,那就動手修改內部的構造方法。
(敲黑板,劃重點) 所有類都有構造方法,不編碼則系統預設生成空的構造方法,若有顯示定義的構造方法,預設的構造方法就會失效
如此,我們只需要顯示的修改構造方法即可,既然不准外部調用,那我們用private修飾構造方法即可
第一步,控制外部實例化我們就做到了
/// <summary> /// 黨派 /// </summary> public class Party { /// <summary> /// 私有化構造函數,不准外部實例化 /// </summary> private Party() { } } }View Code
那我們需要一個主席,一個黨派總不能群龍無首吧,於是我們定義一個主席,並且我們要提供一個訪問的點吧,不然別人想和主席聊兩句都找不到人
第二步,創建一個主席,並提供一個訪問點
/// <summary> /// 黨派 /// </summary> public class Party { /// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman; /// <summary> /// 私有化構造函數,不准外部實例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null)//主席不存在則創建一個主席 { _chairman = new Party(); } return _chairman; } } }View Code
哇哇哇~!!!私有構造函數,為什麼這裡可以調用啊,不是不讓調用麽!!!這是同一個類中,同一個類中,私有,公有,保護,都可以隨意調用。
我們再讓主席講一句話
/// <summary> /// 黨派 /// </summary> public class Party { /// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman; /// <summary> /// 私有化構造函數,不准外部實例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null)//主席不存在則創建一個主席 { _chairman = new Party(); } return _chairman; } public void Say() { Console.WriteLine("同志們好!"); } }View Code
這樣我們的一個簡單的單單例模式就算完成了,我們再看看如何調用
class Program { static void Main(string[] args) { Party chairman = Party.GetChairman(); chairman.Say(); } }View Code
那麼問題來了,單線程操作的時候,以上沒有問題,那多線程的時候呢,多個人同時訪問主席,會不會可能產生多個主席呢,多個主席,是不是就違反了單例模式最基本的原則,僅有一個實例呢。多個主席,難道打一架,勝者為王麽?
於是我們需要再次優化我們的代碼
/// <summary> /// 黨派 /// </summary> public class Party { /// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman; /// <summary> /// 靜態只讀的進程輔助對象 /// </summary> private static readonly object syncRoot = new object(); /// <summary> /// 私有化構造函數,不准外部實例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { lock (syncRoot)//鎖你丫的 { if (_chairman == null)//主席不存在則創建一個主席 { _chairman = new Party(); } } return _chairman; } public void Say() { Console.WriteLine("同志們好!"); } } }View Code
以上,再同一個時刻加了鎖的那部分代碼,只有一個線程可以進入。
如果lock不懂,我知道你們懶
lock 確保當一個線程位於代碼的臨界區時,另一個線程不進入臨界區。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放
那麼問題又來了,為什麼每一次都要鎖,為什麼,見一次主席那麼那麼難麽?能不能沒有創建的時候我才鎖,有主席的時候我直接訪問啊
當然可以,這就是雙重鎖定
/// <summary> /// 黨派 /// </summary> public class Party { /// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman; /// <summary> /// 靜態只讀的進程輔助對象 /// </summary> private static readonly object syncRoot = new object(); /// <summary> /// 私有化構造函數,不准外部實例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null) //主席不存在,我再鎖定,進行選舉 { lock (syncRoot) //鎖你丫的 { if (_chairman == null) //主席不存在則創建一個主席 { _chairman = new Party(); } } } return _chairman; } public void Say() { Console.WriteLine("同志們好!"); } } }View Code
這裡我們就先判斷了主席是否存在,不存在,我們再閉門選舉,等我們選舉出來了,你再來訪問。
我們既優化了執行,也保證了僅有一個主席,保證了多線程訪問的安全性。
其實還有什麼餓漢式,懶漢式的區別
從字面理解
餓漢,一開始我就實例化自己
/// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman = new Party();
懶漢,被引用時候,才實例化自己
/// <summary> /// 我是黨派主席 /// </summary> private static Party _chairman ;View Code
具體使用哪一種,應該根據實際情況來定,無優劣之分,值得註意的是線程安全
總結下
優點:
1、在記憶體里只有一個實例,減少了記憶體的開銷,尤其是頻繁的創建和銷毀實例。
2、避免對資源的多重占用(比如寫文件操作)。
缺點:沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。
使用場景:
1、要求生產唯一序列號。
2、計數器,不用每次刷新都在資料庫裡加一次,用單例先緩存起來。
3、創建的一個對象需要消耗的資源過多,比如 I/O 與資料庫的連接等。
以上就是關於單例模式的分享
一路前行,風雨無阻