天天看點

設計模式之單例模式

目錄:

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

一、什麼是單例模式

  單例模式顧名思義就是隻存在一個執行個體,也就是系統代碼中隻需要一個對象的執行個體應用到全局代碼中,有點類似全局變量。例如,在系統運作時,系統需要讀取配置檔案中的參數,在設計系統的時候讀取配置檔案的類往往設計成單例類。因為系統從啟動運作到結束,隻需要讀取一次配置檔案,這個讀取配置檔案全部由該類負責讀取,在全局代碼中隻需要使用該類即可。這樣不僅簡化了配置檔案的管理,也避免了代碼讀取配置檔案資料的不一緻性。

設計模式之單例模式

 單例模式的特點:

  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的由來,起到抛磚引玉的作用,更好的了解單例模式。總之常用的四種,懶漢,雙重校驗鎖,靜态内部類,枚舉單例。

  餓漢:類加載的時候就建立執行個體,是以是線程安全的,但不能延遲加載。

繼續閱讀