java設計模式一單例模式

單例模式

描述:
  • 作爲對象的創建模式,單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。這個類稱爲單例類。
使用場景:
  • 單例模式只允許創建一個對象,因此節省內存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數據源連接對象等等。如:
1. 需要頻繁實例化然後銷燬的對象。  
2. 創建對象時耗時過多或者耗資源過多,但又經常用到的對象。  
3. 有狀態的工具類對象。 
4. 頻繁訪問數據庫或文件的對象。
  • 應用場景舉例:
1. 資源共享的情況下,避免由於資源操作時導致的性能或損耗等。如上述中的日誌文件,應用配置。 
2. 網站的計數器,一般也是採用單例模式實現,否則難以同步。 
3. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。 
4. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。 
特點
  • 單例類只能有一個實例。
  • 單例類必須自己創建自己的唯一實例。
  • 單例類必須給所有其他對象提供這一實例。
餓漢式單例類
public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();
    /**
     * 私有默認構造子
     */
    private EagerSingleton(){}
    /**
     * 靜態工廠方法
     */
    public static EagerSingleton getInstance(){
        return instance;
    }
}
  • 上面的例子中,在這個類被加載時,靜態變量instance會被初始化,此時類的私有構造子會被調用。這時候,單例類的唯一實例就被創建出來了。

  • 餓漢式其實是一種比較形象的稱謂。既然餓,那麼在創建對象實例的時候就比較着急,餓了嘛,於是在裝載類的時候就創建對象實例。

  • 餓漢式是典型的空間換時間,當類裝載的時候就會創建類的實例,不管你用不用,先創建出來,然後每次調用的時候,就不需要再判斷,節省了運行時間。
懶漢式單例類
public class LazySingleton {
    private static LazySingleton instance = null;
    /**
     * 私有默認構造子
     */
    private LazySingleton(){}
    /**
     * 靜態工廠方法
     */
    public static synchronized LazySingleton getInstance(){
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}
  • 上面的懶漢式單例類實現裏對靜態工廠方法使用了同步化,以處理多線程環境。
  • 懶漢式其實是一種比較形象的稱謂。既然懶,那麼在創建對象實例的時候就不着急。會一直等到馬上要使用對象實例的時候纔會創建,懶人嘛,總是推脫不開的時候纔會真正去執行工作,因此在裝載對象的時候不創建對象實例

  • 懶漢式是典型的時間換空間,就是每次獲取實例都會進行判斷,看是否需要創建實例,浪費判斷的時間。當然,如果一直沒有人使用的話,那就不會創建實例,則節約內存空間

  • 由於懶漢式的實現是線程安全的,這樣會降低整個訪問的速度,而且每次都要判斷。那麼有沒有更好的方式實現呢?

雙重檢查加鎖
  • “雙重檢查加鎖”機制的實現會使用關鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能正確的處理該變量。
public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //先檢查實例是否存在,如果不存在才進入下面的同步塊
        if(instance == null){
            //同步塊,線程安全的創建實例
            synchronized (Singleton.class) {
                //再次檢查實例是否存在,如果不存在才真正的創建實例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 這種實現方式既可以實現線程安全地創建實例,而又不會對性能造成太大的影響。它只是第一次創建實例的時候同步,以後就不需要同步了,從而加快了運行速度。
  • volatile關鍵字的含義是:被其所修飾的變量的值不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存來實現,從而確保多個線程能正確的處理該變量。該關鍵字可能會屏蔽掉虛擬機中的一些代碼優化,所以其運行效率可能不是很高,所以,一般情況下,並不建議使用雙重加鎖機制,酌情使用纔是正理!
類級內部類方式
  • 餓漢式會佔用較多的空間,因爲其在類加載時就會完成實例化,而懶漢式又存在執行速率慢的情況,雙重加鎖機制呢?又有執行效率差的毛病,有沒有一種完美的方式可以規避這些毛病呢?
  • 貌似有的,就是使用類級內部類結合多線程默認同步鎖,同時實現延遲加載和線程安全。
public class ClassInnerClassDanli {
    public static class DanliHolder{
        private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
    }
    private ClassInnerClassDanli(){}
    public static ClassInnerClassDanli getInstance(){
        return DanliHolder.dl;
    }
}
  • 如上代碼,所謂類級內部類,就是靜態內部類,這種內部類與其外部類之間並沒有從屬關係,加載外部類的時候,並不會同時加載其靜態內部類,只有在發生調用的時候纔會進行加載,加載的時候就會創建單例實例並返回,有效實現了懶加載(延遲加載),至於同步問題,我們採用和餓漢式同樣的靜態初始化器的方式,藉助JVM來實現線程安全。

  • 其實使用靜態初始化器的方式會在類加載時創建類的實例,但是我們將實例的創建顯式放置在靜態內部類中,它會導致在外部類加載時不進行實例創建,這樣就能實現我們的雙重目的:延遲加載和線程安全。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章