《設計模式》建立型-單列模式及各類實現

定義

確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例java

使用場景

確保某一個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源,或者某種類型的對象只應該有且只有一個。例如要訪問IO和數據庫等資源,這時候就能夠考慮使用單列模式。數據庫

UML 類圖

classDiagram
Singleton <.. Client
class Singleton{
+getInstanc()Singleton
-Singleton()
}

須要注意的幾個關鍵點:

  1. 構造函數不對外開發,一般爲Private;
  2. 經過靜態方法或者枚舉返回單列類對象;
  3. 確保單列類有且只有一個對象,尤爲是在多線程的環境下;
  4. 確保單列類對象在反序列化時不會從新構建。

單列的實現方式

  1. 餓漢模式
public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}
複製代碼

這種方式在類加載的時候就自行實例化,避免了多線程同步的問題。安全

  1. 懶漢模式
public class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
複製代碼

這種方式的優勢是隻有在第一次使用的時候纔會被實例化,能夠看到 getInstance() 添加了 synchronized 修飾,它是一個同步方法,這是爲了在多線程的狀況下保證單列對象的惟一性,若是隻在單線程使用能夠不加。也是由於添加了 synchronized 致使每次調用getInstance()都須要進行同步,致使沒必要要的同步開銷,因此不建議使用這種寫法。markdown

  1. 雙重檢查模式 DCL(Double Check Lock)
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 關鍵字,若是不用就有可能出現DCL 失效的問題。多線程

是什麼致使DCL 失效的?咱們接着往下看。函數

假設A線程執行到 instance = new Singleton() 這行代碼,這行代碼最終會被編譯成多條指令,大體作如下3鍵事情:性能

  1. 給Singleton 實例分配內存
  2. 調用 Singleton() 構造函數,實例化對象
  3. 將 instance 對象指向分配的內存空間 (這時候 instance 就不爲 null了)

可是因爲Java 編譯器容許處理器亂序執行,這時候就有可能出現執行順序爲1-3-2,當A線程執行完3還未執行2的時候,B線程取走了instance ,在使用時就會出錯,這就是DCL 失效問題。而使用volatile 關鍵字修飾能夠禁止進行指令重排序,全部能夠有效的避免這個問題。固然使用volatile 也會對性能有一點影響,但考慮程序的正確性,這點犧牲是值得的。spa

  1. 靜態內部類模式
public class Singleton{
    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
}
複製代碼

由於靜態內部類不會由於外部類的加載而加載,靜態內部類加載不須要依附於外部類,但在加載靜態內部類時必定會加載外部類。線程

所以使用這種方式能夠確保線程安全,也能保證單列對象的惟一性,同時也延遲了單列的實例化,也不會有性能影響。因此這是最推薦的使用方式。code

  1. 枚舉模式
public enmu Singleton{
    INSTANCE;
    public void doSomething(){
    }
}
複製代碼

枚舉默認是線程安全的並且反序列化也不會致使從新建立對象,保證單列對象的惟一性。若是以前4種模式要杜絕反序列化時從新生成對象,那麼必須加入 readResolve() 函數。

public class Singleton implements Serializable{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
    
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}
複製代碼

小結

以上介紹了單列的5種實現方式,並不是所有。就我的而言,不考慮懶加載的狀況下使用 餓漢模式 便可,不然建議使用 靜態內部類模式,若是須要考慮反序列化的狀況能夠考慮 枚舉模式 ,枚舉模式由於可讀性不太好,因此通常用的比較少。

相關文章
相關標籤/搜索