目錄:
- 什么是單例模式
- 單例模式的應用場景
- 單例模式的優缺點
- 單例模式的實作
- 總借
一、什么是單例模式
單例模式顧名思義就是只存在一個實體,也就是系統代碼中只需要一個物件的實體應用到全域代碼中,有點類似全域變數,例如,在系統運行時,系統需要讀取組態檔中的引數,在設計系統的時候讀取組態檔的類往往設計成單例類,因為系統從啟動運行到結束,只需要讀取一次組態檔,這個讀取組態檔全部由該類負責讀取,在全域代碼中只需要使用該類即可,這樣不僅簡化了組態檔的管理,也避免了代碼讀取組態檔資料的不一致性,

單例模式的特點:
1、該類的構造方法宣告為private,這樣其他類無法初始花該類,只能通過該類的public方法獲取該類的物件,
2、里面有個私有的物件成員,該成員物件是類本身,用于public方法回傳該類的實體,
3、該類中提供一個public的靜態方法,回傳該類的私有成員物件,
二、單例的應用場景
舉一個小例子,在我們的windows桌面上,我們打開了一個回收站,當我們試圖再次打開一個新的回收站時,Windows系統并不會為你彈出一個新的回收站視窗,,也就是說在整個系統運行的程序中,系統只維護一個回收站的實體,這就是一個典型的單例模式運用,
繼續說回收站,我們在實際使用中并不存在需要同時打開兩個回收站視窗的必要性,假如我每次創建回收站時都需要消耗大量的資源,而每個回收站之間資源是共享的,那么在沒有必要多次重復創建該實體的情況下,創建了多個實體,這樣做就會給系統造成不必要的負擔,造成資源浪費,
再舉一個例子,網站的計數器,一般也是采用單例模式實作,如果你存在多個計數器,每一個用戶的訪問都重繪計數器的值,這樣的話你的實計數的值是難以同步的,但是如果采用單例模式實作就不會存在這樣的問題,而且還可以避免執行緒安全問題,同樣多執行緒的執行緒池的設計一般也是采用單例模式,這是由于執行緒池需要方便對池中的執行緒進行控制
同樣,對于一些應用程式的日志應用,或者web開發中讀取組態檔都適合使用單例模式,如HttpApplication 就是單例的典型應用,
從上述的例子中我們可以總結出適合使用單例模式的場景和優缺點:
適用場景:
1.需要生成唯一序列的環境
2.需要頻繁實體化然后銷毀的物件,
3.創建物件時耗時過多或者耗資源過多,但又經常用到的物件,
4.方便資源相互通信的環境
三、單例模式的優缺點
優點:
1、在記憶體中只有一個物件,節省記憶體空間;
2、避免頻繁的創建銷毀物件,可以提高性能;
3、避免對共享資源的多重占用,簡化訪問;
4、為整個系統提供一個全域訪問點,
缺點:
1、不適用于變化頻繁的物件;
2、濫用單例將帶來一些負面問題,如為了節省資源將資料庫連接池物件設計為的單例類,可能會導致共享連接池物件的程式過多而出現連接池溢位;
3、如果實體化的物件長時間不被利用,系統會認為該物件是垃圾而被回收,這可能會導致物件狀態的丟失;
四、單例模式的實作
1、餓漢式
public class Mgr{ //創建自己的實體,并初始化私有靜態final成員 private static final Mgr mgr = new Mgr(); //私有構造方法 private Mgr() {}; //公共方法,回傳自己的實體化成員 public static Mgr getMgr() { return mgr; } }
備注:該單例實作方法簡單明了,推薦使用,該類被JVM加到記憶體的時候,只會加載一次,并且只實體化一個單例,優點是具有執行緒安全性,缺點是:不用他也在記憶體中實體化,浪費記憶體,所以提出了懶散式實作方式,
2、懶漢式
public class Mgr{ //宣告私有靜態物件成員,作為回傳值 private static Mgr mgr; //私有建構式 private Mgr() {}; //懶漢的特點,提供公共靜態方法,如果該成員物件為空,實體化并回傳 public static Mgr getMgr() { if(mgr == null){ mgr = = new Mgr(); } return mgr; } }
備注:(不推薦用)這種實作方法只有程式在呼叫該類的getMgr方法才實體話物件并回傳,特點就是呼叫的時候再實體化并回傳,延遲加載的被動形式,但是該實作方法不是執行緒安全的,因為當同時有有兩個執行緒執行到if(mgr==null)陳述句的時候,由于某些原因其中一個執行緒先一步執行下一句,實體化了物件并回傳;兩一個執行緒再實體化物件在回傳,這兩個執行緒回傳的物件不是同一個物件(這難道還是單例嗎!),所以該實作方法的缺點也很明顯,那為了避免執行緒不安全問題,在懶漢寫法上提出加鎖的實作方式,
3、給公共方法加鎖
public class Mgr{ //宣告私有靜態物件成員,作為回傳值 private static Mgr mgr; //私有建構式 private Mgr() {}; //給公共方法加鎖,只有一個執行緒第一次獲得鎖實體化物件并回傳 public static syncnronized Mgr getMgr() { if(mgr == null){ mgr = new Mgr(); } return mgr; } }
備注:(推薦使用)這種實作方式比較完善,既保證了懶散式的延遲加載方式,也保證了執行緒安全,缺點是在整個方法上加鎖,導致性能下降,因為只有第一次獲得鎖的執行緒實體化物件并回傳,以后的執行緒獲得鎖的時候執行 if(mgr == null)陳述句的時候,由于mgr已經實體化了不為空,直接跳過回傳實體,整個程序要競爭鎖,不能并發執行導致性能下降,那為優化性能下降問題,那我只在mgr = new Mgr()上加鎖,保證鎖粒度最小化的同時保證單例實體化,
4、給實體化加鎖
public class Mgr{ //私有靜態成員物件 private static Mgr mgr; //私有建構式 private Mgr() {}; //公共方法,在實體化陳述句塊加鎖,保證單例 public static Mgr getMgr() { if(mgr == null){ syncnronized(Mgr.class){ mgr = new Mgr(); } } return mgr; } }
備注:(不推薦使用)該實作方法雖然相較方法3性能有所提升,但并不能保證執行緒安全,因為當兩個執行緒同時執行if(mgr == null)陳述句時,其中執行緒1獲取鎖,實體化物件并回傳,執行緒2在獲得鎖又在實體化物件并回傳,執行緒1和執行緒2獲取的物件并不是同一個,所以在此基礎上提出了雙重判斷方式,
5、雙重判斷加鎖
public class Mgr{ //私有靜態成員物件 private static Mgr mgr; //私有建構式 private Mgr() {}; //公共方法提供雙重判斷并在實體化代碼塊加鎖 public static Mgr getMgr() { if(mgr == null){ //第一次判斷 syncnronized(Mgr.class){ if(mgr == null){ //第二次判斷 mgr = = new Mgr(); } } } return mgr; } }
備注:(推薦使用)相較于方法4,該方法雙重判定,如果多執行緒同時執行到第一次判斷陳述句位置,其中一個執行緒獲得鎖,由于是第一次該物件成員為空,實體化后并回傳,其后其它執行緒呼叫公共方法的時候,由于實體化了,在第一次判斷自接回傳實體,不在產生鎖競爭,大大提高了效率,保證了執行緒的安全性,也保證了延遲加載的特性,
6、靜態內部類
public class Mgr{ private Mgr() {}; //定義靜態內部類 private static class MgrHolder{ private final static Mgr mgr = new Mgr(); } //公共方法直接回傳靜態內部類的實體物件 public static Mgr getMgr() { return MgrHolder.mgr; } }
備注:(可使用)該實作方法通過JVM來保證執行緒安全性,靜態內部類MgrHolder來New一個Mgr物件,JVM只會加載一次Mgr類(靜態內部類不會加載),當類呼叫getMgr方法的時候,也只會呼叫一次,公共方法呼叫靜態內部類,獲取一個物件(也是實作懶加載),所以也是執行緒安全的,
7、列舉類單例模式
public enum Mgr{ mgr; public void m(){} //業務方法 }
備注:(推薦使用)jdk1.5之后才能正常達到單例效果,參考來自《Effective Java》,注意列舉類的列舉變數必須寫在第一行,后面實作業務代碼,呼叫方式是:Mgr.mgr.Function_Name();具備列舉型別的特點,有點是:執行緒同步,防止反序列化,
五、總結
通過上面幾種單例模式的實作方式的列舉,但是在實際應用中其中的2,3,4三種方式并不適用,列出來只是讓讀者更好的理解方式5的由來,起到拋磚引玉的作用,更好的理解單例模式,總之常用的四種,懶漢,雙重校驗鎖,靜態內部類,列舉單例,
餓漢:類加載的時候就創建實體,所以是執行緒安全的,但不能延遲加載,
雙重校驗鎖:執行緒安全,效率較高,延遲加載,
靜態內部類:實作起來比較麻煩,在不同的編譯器上會出現不可預知的錯誤,
列舉單例:很好,不僅避免了多執行緒同步問題,而且能反正反序列化重新創建物件,但是不能延遲加載,用的人少,
- 讀者發現有什么有問題的地方謝謝留言指正,部分參考自:https://www.cnblogs.com/xuwendong/p/9633985.html#_label0

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/6005.html
標籤:設計模式
下一篇:軟體設計模式修煉 -- 狀態模式
