目錄:html
單例模式顧名思義就是隻存在一個實例,也就是系統代碼中只須要一個對象的實例應用到全局代碼中,有點相似全局變量。例如,在系統運行時,系統須要讀取配置文件中的參數,在設計系統的時候讀取配置文件的類每每設計成單例類。由於系統從啓動運行到結束,只須要讀取一次配置文件,這個讀取配置文件所有由該類負責讀取,在全局代碼中只須要使用該類便可。這樣不只簡化了配置文件的管理,也避免了代碼讀取配置文件數據的不一致性。web
單例模式的特色:數據庫
一、該類的構造方法聲明爲private,這樣其餘類沒法初始花該類,只能經過該類的public方法獲取該類的對象。windows
二、裏面有個私有的對象成員,該成員對象是類自己,用於public方法返回該類的實例。安全
三、該類中提供一個public的靜態方法,返回該類的私有成員對象。多線程
舉一個小例子,在咱們的windows桌面上,咱們打開了一個回收站,當咱們試圖再次打開一個新的回收站時,Windows系統並不會爲你彈出一個新的回收站窗口。,也就是說在整個系統運行的過程當中,系統只維護一個回收站的實例。這就是一個典型的單例模式運用。併發
繼續說回收站,咱們在實際使用中並不存在須要同時打開兩個回收站窗口的必要性。假如我每次建立回收站時都須要消耗大量的資源,而每一個回收站之間資源是共享的,那麼在沒有必要屢次重複建立該實例的狀況下,建立了多個實例,這樣作就會給系統形成沒必要要的負擔,形成資源浪費。函數
再舉一個例子,網站的計數器,通常也是採用單例模式實現,若是你存在多個計數器,每個用戶的訪問都刷新計數器的值,這樣的話你的實計數的值是難以同步的。可是若是採用單例模式實現就不會存在這樣的問題,並且還能夠避免線程安全問題。一樣多線程的線程池的設計通常也是採用單例模式,這是因爲線程池須要方便對池中的線程進行控制性能
一樣,對於一些應用程序的日誌應用,或者web開發中讀取配置文件都適合使用單例模式,如HttpApplication 就是單例的典型應用。優化
從上述的例子中咱們能夠總結出適合使用單例模式的場景和優缺點:
適用場景:
1.須要生成惟一序列的環境
2.須要頻繁實例化而後銷燬的對象。
3.建立對象時耗時過多或者耗資源過多,但又常常用到的對象。
4.方便資源相互通訊的環境
優勢:
一、在內存中只有一個對象,節省內存空間;
二、避免頻繁的建立銷燬對象,能夠提升性能;
三、避免對共享資源的多重佔用,簡化訪問;
四、爲整個系統提供一個全局訪問點。
缺點:
一、不適用於變化頻繁的對象;
二、濫用單例將帶來一些負面問題,如爲了節省資源將數據庫鏈接池對象設計爲的單例類,可能會致使共享鏈接池對象的程序過多而出現鏈接池溢出;
三、若是實例化的對象長時間不被利用,系統會認爲該對象是垃圾而被回收,這可能會致使對象狀態的丟失;
一、餓漢式
public class Mgr{ //建立本身的實例,並初始化私有靜態final成員 private static final Mgr mgr = new Mgr(); //私有構造方法 private Mgr() {}; //公共方法,返回本身的實例化成員 public static Mgr getMgr() { return mgr; } }
備註:該單例實現方法簡單明瞭,推薦使用。該類被JVM加到內存的時候,只會加載一次,而且只實例化一個單例,優勢是具備線程安全性,缺點是:不用他也在內存中實例化,浪費內存。因此提出了懶散式實現方式。
二、懶漢式
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)語句的時候,因爲某些緣由其中一個線程先一步執行下一句,實例化了對象並返回;兩一個線程再實例化對象在返回,這兩個線程返回的對象不是同一個對象(這難道仍是單例嗎!),因此該實現方法的缺點也很明顯。那爲了不線程不安全問題,在懶漢寫法上提出加鎖的實現方式。
三、給公共方法加鎖
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()上加鎖,保證鎖粒度最小化的同時保證單例實例化。
四、給實例化加鎖
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獲取的對象並非同一個。因此在此基礎上提出了雙重判斷方式。
五、雙重判斷加鎖
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,該方法雙重斷定,若是多線程同時執行到第一次判斷語句位置,其中一個線程得到鎖,因爲是第一次該對象成員爲空,實例化後並返回。其後其它線程調用公共方法的時候,因爲實例化了,在第一次判斷自接返回實例,不在產生鎖競爭。大大提升了效率,保證了線程的安全性,也保證了延遲加載的特性。
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方法的時候,也只會調用一次,公共方法調用靜態內部類,獲取一個對象(也是實現懶加載)。因此也是線程安全的。
七、枚舉類單例模式
public enum Mgr{ mgr; public void m(){} //業務方法 }
備註:(推薦使用)jdk1.5以後才能正常達到單例效果,參考來自《Effective Java》。注意枚舉類的枚舉變量必須寫在第一行,後面實現業務代碼。調用方式是:Mgr.mgr.Function_Name();具有枚舉類型的特色,有點是:線程同步,防止反序列化。
經過上面幾種單例模式的實現方式的列舉,可是在實際應用中其中的2,3,4三種方式並不適用,列出來只是讓讀者更好的理解方式5的由來,起到拋磚引玉的做用,更好的理解單例模式。總之經常使用的四種,懶漢,雙重校驗鎖,靜態內部類,枚舉單例。
餓漢:類加載的時候就建立實例,因此是線程安全的,但不能延遲加載。
雙重校驗鎖:線程安全,效率較高,延遲加載。
靜態內部類:實現起來比較麻煩,在不一樣的編譯器上會出現不可預知的錯誤。
枚舉單例:很好,不只避免了多線程同步問題,並且能反正反序列化從新建立對象,可是不能延遲加載,用的人少。