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