设计模式之单件模式(Singleton Pattern)
一.單件模式是什么?
單件模式也被稱為單例模式,它的作用說白了就是為了確保“該類的實例只有一個”
單件模式經(jīng)常被用來管理資源敏感的對象,比如:數(shù)據(jù)庫連接對象、注冊表對象、線程池對象等等,這種對象如果同時存在多個的話就會造成各種不一致的麻煩(你總不希望發(fā)生數(shù)據(jù)庫重復(fù)連接的異常吧)
二.如何保證類的實例只有一個?
(這個問題看似簡單,但如果沒有接觸過單件模式的話,要自己想出來解決方案還是需要一些天賦的。。不信的話,可以試著想想。。)
1.類的實例可能只有一個嗎?貌似只要知道類名就可以隨便new了吧?
當(dāng)然可以,知道類名的話,確實可以調(diào)用其構(gòu)造方法來new實例,但是,注意一點:這個類必須要有公開的構(gòu)造方法才能從外部new實例,不是嗎?
2.那就是說要保證類的實例只有一個的話,這個類不能有公開的構(gòu)造方法,對吧?
沒錯,就是這樣,我們需要定義一個私有的構(gòu)造方法
3.一個沒有公開構(gòu)造方法的類能夠產(chǎn)生實例嗎?如果構(gòu)造方法是private,那么只有該類的實例才能調(diào)用這個構(gòu)造方法,同樣的要調(diào)用這個構(gòu)造方法才能產(chǎn)生該類的實例。。這不是“雞生蛋,蛋生雞。。”的問題嗎?
用私有的構(gòu)造方法當(dāng)然可以生產(chǎn)實例,上面忽略了一點:并不是“只有該類的實例才能調(diào)用這個構(gòu)造方法”
因為在該類內(nèi)部就可以隨便調(diào)用這個私有的構(gòu)造方法,并不需要創(chuàng)建任何實例
-------
有了上面的討論結(jié)果,我們就可以實現(xiàn)經(jīng)典的單件模式了:
package SingletonPattern;/*** @author ayqy* 最經(jīng)典的單件模式*/ public class Singleton {private static Singleton instance;//定義靜態(tài)實例變量/*** 定義私有構(gòu)造方法,防止從外部new實例*/private Singleton(){//初始化操作}/*** 提供全局訪問點* @return 該類的實例*/public static Singleton getInstance(){if(instance == null)instance = new Singleton();return instance;}/** 其它有用的屬性和行為* 畢竟應(yīng)用了單件模式的類仍然具有原本的功能* */ }注意:一定要清楚最后一點,應(yīng)用了單件模式的類并不應(yīng)該喪失其原本的功能,千萬不能為了使用而使用
三.繼續(xù)思考我們的單件模式
我們的單件模式已經(jīng)萬無一失了嗎?
不,它還存在很多問題,比如:
1.多線程環(huán)境下
2.多個class loader環(huán)境下
我們無法保證產(chǎn)生的實例只有一個,對吧?
但是作為一種成熟的設(shè)計模式,單件模式必須要能從容應(yīng)對這些環(huán)境,所以,接下來我們將討論如何應(yīng)對這些環(huán)境
四.多線程環(huán)境下的單件模式
如何在多線程環(huán)境下保證實例的唯一性?
很容易想到用synchronized關(guān)鍵字來保證線程安全,就像這樣:
/*** 提供全局訪問點* @return 該類的實例*/ public static synchronized Singleton getInstance(){if(instance == null)instance = new Singleton();return instance; }我們把getInstance方法定義為同步方法就保證了不會有多個線程同時進(jìn)入該方法,就不會產(chǎn)生不同的實例了
-------
但是上面的方法存在致命的問題:用synchronized關(guān)鍵字同步方法會極大的降低效率(同步一個方法甚至可能造成百倍的效率下降。。),這會拖垮我們的程序
有什么好的改進(jìn)方法呢?
首先,上面的同步塊是整個getIntance方法,每次調(diào)用該方法都會強(qiáng)制進(jìn)入同步機(jī)制,但仔細(xì)一想,我們只在第一此調(diào)用該方法時需要進(jìn)行同步(第一次new對象),之后的調(diào)用直接返回new好的對象就好了
那么,我們的改進(jìn)方案就是:用雙重加鎖實現(xiàn)只在第一次new對象時進(jìn)行同步
package SingletonPattern;/*** @author ayqy* 多線程下的單件模式2——利用雙重加鎖保證只在實例化變量的時候進(jìn)行同步*/ public class DoubleLockSingleton {private static volatile DoubleLockSingleton instance;//定義靜態(tài)實例變量/*** 定義私有構(gòu)造方法,防止從外部new實例*/private DoubleLockSingleton(){//初始化操作}/*** 提供全局訪問點* @return 該類的實例*/public static DoubleLockSingleton getInstance(){if(instance == null)synchronized(DoubleLockSingleton.class){//進(jìn)入同步塊if(instance == null)//再次判空instance = new DoubleLockSingleton();}return instance;}/** 其它有用的屬性和行為* 畢竟應(yīng)用了單件模式的類仍然具有原本的功能* */ }注意:雙重加鎖體現(xiàn)在volatile關(guān)鍵字(告訴編譯器,這個變量不能被保留副本,一旦發(fā)生變動就會強(qiáng)制寫回,避免了不一致)和synchronized修飾的同步塊
但要明白這樣做的代價,volatile關(guān)鍵字也會告訴編譯器,不要對該對象進(jìn)行編譯優(yōu)化
只看第一次new對象的過程的話,雙重加鎖的效率甚至要比同步方法更低,但在雙重加鎖方式在以后的調(diào)用中不再需要進(jìn)行同步,所以長遠(yuǎn)看來雙重加鎖的效率要高于同步方法
-------
有沒有一種方法不需要使用龜速的同步機(jī)制就能保證線程安全呢?如果有的話,絕對能夠大大提高效率,對吧?
當(dāng)然有,這種方法叫做“急切初始化”(順便提一下,開篇提到的“經(jīng)典單件模式”其實用了“延遲初始化”的方法。。很簡單,不必解釋),一起看看吧:
package SingletonPattern;/*** @author ayqy* 多線程環(huán)境下的單件模式——用“急切初始化”來保證線程安全*/ public class EagerlyInitSingleton {private static EagerlyInitSingleton instance = new EagerlyInitSingleton();//定義靜態(tài)實例變量,并在類加載的時候就進(jìn)行初始化操作/*** 定義私有構(gòu)造方法,防止從外部new實例*/private EagerlyInitSingleton(){//初始化操作}/*** 提供全局訪問點* @return 該類的實例*/public static synchronized EagerlyInitSingleton getInstance(){return instance;}/** 其它有用的屬性和行為* 畢竟應(yīng)用了單件模式的類仍然具有原本的功能* */ }額,這也能叫方法嗎?這么做貌似不和標(biāo)準(zhǔn)吧?
沒關(guān)系,這種方法自然有它的優(yōu)點,比如:
1.效率很高,且線程安全
2.簡單易用,什么都不用考慮,甚至不用判斷
但其致命的缺點是:資源浪費問題,如果這個對象是一個巨大的極其耗費資源的對象,而我們在一開始就創(chuàng)建了它,卻遲遲沒有用到,這將是非常傷的。。
-------
上面提到了三種保證線程同步的方式,如何選擇必須要結(jié)合具體情況來定,應(yīng)綜合考慮效率,資源利用等各個因素
五.多個class loader環(huán)境下的單件模式
如果存在多個類加載器,多個類加載器可能同時加載我們的單件類,從而產(chǎn)生多個實例
對于這種情況,我們可以顯式指定使用哪一個class loader來加載單件類,這樣就有效避免了上述問題
六.總結(jié)
應(yīng)用單件模式可以保證對象的唯一性,但要注意單件模式的適用范圍
不應(yīng)該濫用單件模式,因為畢竟需要管理的資源敏感對象不會很多
轉(zhuǎn)載于:https://www.cnblogs.com/ayqy/p/3962910.html
總結(jié)
以上是生活随笔為你收集整理的设计模式之单件模式(Singleton Pattern)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: My.Ioc 代码示例——避免循环依赖
- 下一篇: asp.net中两款文本编辑器NicEd