【设计模式】单例模式 Singleton Pattern
通常我們?cè)趯懗绦虻臅r(shí)候會(huì)碰到一個(gè)類只允許在整個(gè)系統(tǒng)中只存在一個(gè)實(shí)例(Instance)? 的情況, 比如說(shuō)我們想做一計(jì)數(shù)器,統(tǒng)計(jì)某些接口調(diào)用的次數(shù),通常我們的數(shù)據(jù)庫(kù)連接也是只期望有一個(gè)實(shí)例。Windows系統(tǒng)的系統(tǒng)任務(wù)管理器也是始終只有一個(gè),如果你打開(kāi)了windows管理器,你再想打開(kāi)一個(gè)那么他還是同一個(gè)界面(同一個(gè)實(shí)例), 還有比如 做.Net平臺(tái)的人都知道,AppDomain 對(duì)象,一個(gè)系統(tǒng)中也只有一個(gè),所有的類庫(kù)都會(huì)加載到AppDomain中去運(yùn)行。只需要一個(gè)實(shí)例對(duì)象的場(chǎng)景,隨處可見(jiàn),那么有么有什么好的解決方法來(lái)應(yīng)對(duì)呢? 有的,那就是 單例模式。
一、單例模式定義
單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類,它提供全局訪問(wèn)的方法。單例模式是一種對(duì)象創(chuàng)建型模式。
二、單例模式結(jié)構(gòu)圖
- Singleton(單例):在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例,同時(shí)它提供一個(gè)靜態(tài)的GetInstance()工廠方法,讓客戶可以訪問(wèn)它的唯一實(shí)例;為了防止在外部對(duì)其實(shí)例化,將其構(gòu)造函數(shù)設(shè)計(jì)為私有(private);在單例類內(nèi)部定義了一個(gè)Singleton類型的靜態(tài)對(duì)象,作為外部共享的唯一實(shí)例。
三、 單例模式典型代碼
public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton GetInstance(){if(instance==null){instance=new Singleton();}return instance;} }客戶端調(diào)用代碼:
static void Main(string[] args) {Singleton singleto = Singleton.GetInstance(); }在C#中經(jīng)常將統(tǒng)一訪問(wèn)點(diǎn)暴露出一個(gè)只讀的屬性供客戶端程序使用,這樣代碼就變成了這樣:
public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton GetInstance{get{if (instance == null){instance = new Singleton();}return instance;}} }客戶端調(diào)用:
static void Main(string[] args) {Singleton singleton = Singleton.GetInstance; }四、單例模式實(shí)例
1. 懶漢模式
假如我們要做一個(gè)程序計(jì)數(shù)器,一旦程序啟動(dòng)無(wú)論多少個(gè)客戶端調(diào)用這個(gè) 計(jì)數(shù)器計(jì)數(shù)的結(jié)果始終都是在前一個(gè)的基礎(chǔ)上加1,那么這個(gè)計(jì)數(shù)器類就可以設(shè)計(jì)成一個(gè)單例模式的類。
public class SingletonCounter {private static SingletonCounter instance;private static int number=0;private SingletonCounter() { }public static SingletonCounter Instance{get{if (instance == null) instance = new SingletonCounter();number++;return instance;}}public int GetCounter(){return number;} }客戶端調(diào)用:
static void Main(string[] args) {//App A call the counter;SingletonCounter singletonA = SingletonCounter.Instance;int numberA = singletonA.GetCounter();Console.WriteLine("App A call the counter get number was:" + numberA);//App B call the counter;SingletonCounter singletonB = SingletonCounter.Instance;int numberB = singletonA.GetCounter();Console.WriteLine("App B call the counter get number was:" + numberB);Console.ReadKey(); }輸出結(jié)果:
這個(gè)實(shí)現(xiàn)是線程不安全的,如果有多個(gè)線程同時(shí)調(diào)用,并且又恰恰在計(jì)數(shù)器初始化的瞬間多個(gè)線程同時(shí)檢測(cè)到了 instance==null為true情況,會(huì)怎樣呢?這就是下面要討論的 “加鎖懶漢模式”
2、加鎖懶漢模式
多個(gè)線程同時(shí)調(diào)用并且同時(shí)檢測(cè)到 instance == null 為 true的情況,那后果就是會(huì)出現(xiàn)多個(gè)實(shí)例了,那么就無(wú)法保證唯一實(shí)例了,解決這個(gè)問(wèn)題就是增加一個(gè)對(duì)象鎖來(lái)確保在創(chuàng)建的過(guò)程中只有一個(gè)實(shí)例。(鎖可以確保鎖住的代碼塊是線程獨(dú)占訪問(wèn)的,如果一個(gè)線程占有了這個(gè)鎖,其它線程只能等待該線程釋放鎖以后才能繼續(xù)訪問(wèn))。
public class SingletonCounter {private static SingletonCounter instance;private static readonly object locker = new object();private static int number = 0;private SingletonCounter() { }public static SingletonCounter Instance{get{lock (locker){if (instance == null) instance = new SingletonCounter();number++;return instance;}}}public int GetCounter(){return number;} }客戶端調(diào)用代碼:
static void Main(string[] args) { for (int i = 1; i < 100; i++){var task = new Task(() =>{SingletonCounter singleton = SingletonCounter.Instance;int number = singleton.GetCounter();Console.WriteLine("App call the counter get number was:" + number);});task.Start();}Console.ReadKey(); }輸出結(jié)果:
這種模式是線程安全,即使在多線程的情況下仍然可以保持單個(gè)實(shí)例。那么這種模式會(huì)不會(huì)有什么問(wèn)題呢?假如系統(tǒng)的訪問(wèn)量非常大,并發(fā)非常高,那么計(jì)數(shù)器就會(huì)是一個(gè)性能瓶頸,因?yàn)閷?duì)鎖會(huì)使其它的線程無(wú)法訪問(wèn)。在訪問(wèn)量不大,并發(fā)量不高的系統(tǒng)尚可應(yīng)付,如果高訪問(wèn)量,高并發(fā)的情況下這樣做肯定是不行的,那么有什么辦法改進(jìn)呢?這就是下面要討論的“雙檢查加鎖懶漢模式”。
3、雙檢查加鎖懶漢模式
加鎖懶漢模式雖然保證了系統(tǒng)的線程安全,但是卻為系統(tǒng)帶來(lái)了新能問(wèn)題,主要的性能來(lái)自鎖帶來(lái)開(kāi)銷,雙檢查就是解決這個(gè)鎖帶來(lái)的問(wèn)題,在鎖之前再做一次 instance==null的檢查,如果返回true就直接返回 單例對(duì)象了,避開(kāi)了無(wú)謂的鎖, 我們來(lái)看下,雙檢查懶漢模式代碼:
public class DoubleCheckLockSingletonCounter {private static DoubleCheckLockSingletonCounter instance;private static readonly object locker = new object();private static int number = 0;private DoubleCheckLockSingletonCounter() { }public static DoubleCheckLockSingletonCounter Instance{get{if (instance == null){lock (locker){if (instance == null){instance = new DoubleCheckLockSingletonCounter();}}}number++;return instance;}}public int GetCounter(){return number;} }客戶端調(diào)用代碼和“懶漢加鎖模式”相同,輸出結(jié)果也相同。
4、餓漢模式
單例模式除了我們上面講的三種懶漢模式外,還有一種叫“餓漢模式”的實(shí)現(xiàn)方式,“餓漢模式”直接在Singleton類里實(shí)例化了當(dāng)前類的實(shí)例,并且保存在一個(gè)靜態(tài)對(duì)象中,因?yàn)槭庆o態(tài)對(duì)象,所以在程序啟動(dòng)的時(shí)候就已經(jīng)實(shí)例化好了,后面直接使用,因此不存在線程安全的問(wèn)題。
下面是“餓漢模式”的代碼實(shí)現(xiàn):
public class EagerSingletonCounter {private static EagerSingletonCounter instance = new EagerSingletonCounter();private static int number = 0;private EagerSingletonCounter() { }public static EagerSingletonCounter Instance{get{number++;return instance;}}public int GetCounter(){return number;} }?
五、單例模式應(yīng)用場(chǎng)景
單例模式只有一個(gè)角色非常簡(jiǎn)單,使用的場(chǎng)景也很明確,就是一個(gè)類只需要、且只能需要一個(gè)實(shí)例的時(shí)候使用單例模式。
六、擴(kuò)展
?
1、”餓漢模式“和”懶漢模式“的比較
”餓漢模式“在程序啟動(dòng)的時(shí)候就已經(jīng)實(shí)例化好了,并且一直駐留在系統(tǒng)中,客戶程序調(diào)用非常快,因?yàn)樗庆o態(tài)變量,雖然完美的保證線程的安全,但是如果創(chuàng)建對(duì)象的過(guò)程很復(fù)雜,要占領(lǐng)系統(tǒng)或者網(wǎng)絡(luò)的一些昂貴的資源,但是在系統(tǒng)中使用的頻率又極低,甚至系統(tǒng)運(yùn)行起來(lái)后都不會(huì)去使用該功能,那么這樣一來(lái),啟動(dòng)之后就一直占領(lǐng)著系統(tǒng)的資源不釋放,這有些得不償失。
“懶漢模式“ 恰好解決了”餓漢模式“這種占用資源的問(wèn)題,”懶漢模式”將類的實(shí)例化延遲到了運(yùn)行時(shí),在使用時(shí)的第一次調(diào)用時(shí)才創(chuàng)建出來(lái)并一直駐留在系統(tǒng)中,但是為了解決線程安全問(wèn)題, 使用對(duì)象鎖也是 影響了系統(tǒng)的性能。這兩種模式各有各的好處,但是又各有其缺點(diǎn)。
有沒(méi)有一種折中的方法既可以避免一開(kāi)始就實(shí)例化且一直占領(lǐng)系統(tǒng)資源,又沒(méi)有性能問(wèn)題的Singleton呢? 答案是:有的。
2、第三種選擇
“餓漢模式“類不能實(shí)現(xiàn)延遲加載,不管用不用始終占據(jù)內(nèi)存;”懶漢式模式“類線程安全控制煩瑣,而且性能受影響。我們用一種折中的方法來(lái)解決這個(gè)問(wèn)題,針對(duì)主要矛盾, 即:既可以延時(shí)加載又不影響性能。
在Singleton的內(nèi)部創(chuàng)建一個(gè)私有的靜態(tài)類用于充當(dāng)單例類的”初始化器“,專門用來(lái)創(chuàng)建Singleton的實(shí)例:
public class BestPracticeSingletonCounter {private static class SingletonInitializer{public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter();} private static int number = 0;private BestPracticeSingletonCounter() { }public static BestPracticeSingletonCounter Instance{get{number++;return SingletonInitializer.instance;}}public int GetCounter(){return number;} }這種模式兼具了”餓漢“和”懶漢“模式的優(yōu)點(diǎn)有摒棄了其缺點(diǎn),可以說(shuō)是一個(gè)完美的實(shí)現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的【设计模式】单例模式 Singleton Pattern的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 分布式缓存的面试题9
- 下一篇: WPF 打印实例