設計模式之單例模式

目錄:html

  • 什麼是單例模式
  • 單例模式的應用場景
  • 單例模式的優缺點
  • 單例模式的實現
  • 總借

1、什麼是單例模式

  單例模式顧名思義就是隻存在一個實例,也就是系統代碼中只須要一個對象的實例應用到全局代碼中,有點相似全局變量。例如,在系統運行時,系統須要讀取配置文件中的參數,在設計系統的時候讀取配置文件的類每每設計成單例類。由於系統從啓動運行到結束,只須要讀取一次配置文件,這個讀取配置文件所有由該類負責讀取,在全局代碼中只須要使用該類便可。這樣不只簡化了配置文件的管理,也避免了代碼讀取配置文件數據的不一致性。web

 

 單例模式的特色:數據庫

  一、該類的構造方法聲明爲private,這樣其餘類沒法初始花該類,只能經過該類的public方法獲取該類的對象。windows

  二、裏面有個私有的對象成員,該成員對象是類自己,用於public方法返回該類的實例。安全

  三、該類中提供一個public的靜態方法,返回該類的私有成員對象。多線程

 

2、單例的應用場景

 

  舉一個小例子,在咱們的windows桌面上,咱們打開了一個回收站,當咱們試圖再次打開一個新的回收站時,Windows系統並不會爲你彈出一個新的回收站窗口。,也就是說在整個系統運行的過程當中,系統只維護一個回收站的實例。這就是一個典型的單例模式運用。併發

 

  繼續說回收站,咱們在實際使用中並不存在須要同時打開兩個回收站窗口的必要性。假如我每次建立回收站時都須要消耗大量的資源,而每一個回收站之間資源是共享的,那麼在沒有必要屢次重複建立該實例的狀況下,建立了多個實例,這樣作就會給系統形成沒必要要的負擔,形成資源浪費。函數

 

  再舉一個例子,網站的計數器,通常也是採用單例模式實現,若是你存在多個計數器,每個用戶的訪問都刷新計數器的值,這樣的話你的實計數的值是難以同步的。可是若是採用單例模式實現就不會存在這樣的問題,並且還能夠避免線程安全問題。一樣多線程的線程池的設計通常也是採用單例模式,這是因爲線程池須要方便對池中的線程進行控制性能

 

  一樣,對於一些應用程序的日誌應用,或者web開發中讀取配置文件都適合使用單例模式,如HttpApplication 就是單例的典型應用。優化

 

  從上述的例子中咱們能夠總結出適合使用單例模式的場景和優缺點:  

 

   適用場景:

  1.須要生成惟一序列的環境

  2.須要頻繁實例化而後銷燬的對象。

  3.建立對象時耗時過多或者耗資源過多,但又常常用到的對象。 

  4.方便資源相互通訊的環境

 

 

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();具有枚舉類型的特色,有點是:線程同步,防止反序列化。

5、總結

  經過上面幾種單例模式的實現方式的列舉,可是在實際應用中其中的2,3,4三種方式並不適用,列出來只是讓讀者更好的理解方式5的由來,起到拋磚引玉的做用,更好的理解單例模式。總之經常使用的四種,懶漢,雙重校驗鎖,靜態內部類,枚舉單例。

  餓漢:類加載的時候就建立實例,因此是線程安全的,但不能延遲加載。

  雙重校驗鎖:線程安全,效率較高,延遲加載。

  靜態內部類:實現起來比較麻煩,在不一樣的編譯器上會出現不可預知的錯誤。

  枚舉單例:很好,不只避免了多線程同步問題,並且能反正反序列化從新建立對象,可是不能延遲加載,用的人少。

 

 

相關文章
相關標籤/搜索