天天看點

設計模式學習筆記(五)——單例模式

有一些對象我們隻需要使用一個,例如線程池,緩存,對話框,日志對象等等。如果這些對象有多個執行個體,就會導緻許多問題産生,像程式的行為異常,記憶體溢出,或者是不一緻的結果。

使用全局變量可以達到效果,但同時,全局變量在程式一開始就被建立,如果這是一個占資源大的對象,而在這次執行中程式沒有使用到該變量,這就形成了資源浪費。

定義: 單例模式確定類隻有一個執行個體,并提供一個全局通路點。

經典的單例模式實作

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton(){}

    public static Singleton getInstance(){
        if (uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    //其他方法
}
           

上面就是一個經典的單例模式,私有構造器,共有建立方法,當類變量為空時,我們建立一個執行個體并傳回它,我們稱這種方式為延遲執行個體化,也就是”餓漢模式“。下面再看一個例子

原始代碼

public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;

    public ChocolateBoiler() {
        empty = true;
        boiled = false;
    }
    public void fill(){
        if (isEmpty()){
            empty = false;
            boiled = false;
        }
    }
    public void drain(){
        if(!isEmpty()&&!isBoiled()){
            empty = true;
        }
    }
    public void boil(){
        if(!isEmpty()&&!isBoiled()){
            boiled = true;
        }
    }
    public boolean isEmpty(){
        return empty;
    }
    public boolean isBoiled(){
        return boiled;
    }
}
           

為了使得不出現不一緻的情況,我們決定使用單例模式,如下

懶漢模式

public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;
    private static ChocolateBoiler instance;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static ChocolateBoiler getInstance(){
        if(instance == null){
            instance = new ChocolateBoiler();
        }
        return instance;
    }
    //其他方法
}
           

出現問題了: 我們的fill()方法竟然允許在boil()時候繼續添加原料,這不符合我們的邏輯。

經過檢查,我們發現這是由于有兩個線程同時去擷取這個執行個體造成的錯誤,解決方法如下

加鎖

public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;
    private static ChocolateBoiler instance;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static synchronized ChocolateBoiler getInstance(){
        if(instance == null){
            instance = new ChocolateBoiler();
        }
        return instance;
    }
    //其他方法
}
           

在方法上加鎖能確定同時隻有一個線程能夠拿到該執行個體。但是,使用synchronized會降低性能,而我們隻有第一次執行該方法時才需要同步,之後每次調用該方法都會造成對性能的浪費。

“餓漢模式”

public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;
    private static ChocolateBoiler instance = new ChocolateBoiler();

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static synchronized ChocolateBoiler getInstance(){
        return instance;
    }
}
           

雙重加鎖檢查

public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;
    private volatile static ChocolateBoiler instance;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static ChocolateBoiler getInstance(){
        if(instance == null){
            synchronized (ChocolateBoiler.class){
                if(instance == null){
                    instance = new ChocolateBoiler();
                }
            }
        }
        return instance;
    }
}
           

上例使用了雙重檢查,首先檢查是否建立了執行個體,隻有未建立的時候才會進入同步塊。這樣就大大降低了性能消耗