banner
RustyNail

RustyNail

coder. 【blog】https://rustynail.me 【nostr】wss://ts.relays.world/ wss://relays.world/nostr

設計模式-單例模式

有時候,我們希望某一個類只能產生一個物件。比如說一個學校的校長只能有一個。

初步#

要一個類只能有一個實例,也就是說我們要把它的構造器隱藏起來:private Something(){}

然後提供一個對外的方法,用來實際的產生 / 返回物件。也就是說我們可以得到下邊的實現:

public class Something {
    private static Something INSTANCE = null;
    private Something(){}

    public static Something getINSTANCE() {
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
        return INSTANCE;
    }

}

這是一個單例模式的簡單實現。我們發現,每次getINSTANCE()的時候,都要判斷INSTANCE == null

所以我們也可以這樣(如果你的單例物件足夠小,而且運行期間都需要):

public class Something {
    private static Something INSTANCE = new Something();
    private Something(){}

    public static Something getINSTANCE() {
        return INSTANCE;
    }
}

多線程下的單例模式#

上邊的單例模式實現在多線程下是不安全的。因為不能保證INSTANCE的值不會改變。解決線程問題的方法,很簡單,加鎖。

比如,我們給getINSTANCE方法加鎖。:

public static synchronized Something getINSTANCE() {
    if (INSTANCE == null){
       INSTANCE = new Something();
    }
    return INSTANCE;
}

或者在判斷 null 的時候加鎖:

public static Something getINSTANCE() {
    synchronized (Something.class){
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
    }
    return INSTANCE;
}

但是,這樣有個問題,就是每次獲取實例的時候,都有鎖操作,會耗費資源,假如,這個方法被頻繁使用,會有性能問題。

把代碼改一下:

public static Something getINSTANCE() {
    if (INSTANCE == null){
        synchronized (Something.class){
            if (INSTANCE == null){
                INSTANCE = new Something();
            }
        }
    }
    return INSTANCE;
}

這樣寫的好處是:只有在INSTANCE為 null 的時候才會加鎖處理。也就是所只起作用一次。

使用枚舉實現單例#

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

使用枚舉來實現單例,在類真正被用到的時候才會加載物件(利用類加載機制),加載過程是同步的,是線程安全的。

前邊的單例在面對序列化、反序列化的時候還是不安全的,但是對於枚舉,直接規定不能反序列化枚舉物件。。。

public abstract class Enum<E extends Enum<E>>  implements Comparable<E>, Serializable {  
   // 略
  
    // 不允許反序列化枚舉物件
    private void readObject(ObjectInputStream in) throws IOException,  
        ClassNotFoundException {  
            throw new InvalidObjectException("can't deserialize enum");  
    }  
  
    // 不允許反序列化枚舉物件  
    private void readObjectNoData() throws ObjectStreamException {  
        throw new InvalidObjectException("can't deserialize enum");  
    }  
 
    // 枚舉類不可以有finalize方法,子類不可以重寫該方法  保證實例的物件唯一
    protected final void finalize() { }  
}  
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。