23種設計模式之單例模式

單例模式屬於建立型模式,保證在程序運行期間一個類只有一個實例,並提供一個全局訪問點java

推薦訪問個人我的網站,排版更好看呦:
https://chenmingyu.top/design-singleton/面試

什麼是單例模式

單例模式目的是保證在程序運行期間一個類只有一個實例,並提供一個全局訪問點,不管什麼狀況下,只會生成一個實例,免去繁瑣的建立銷燬對象的過程。安全

如何設計單例

如何設計單例模式其實很簡單,只須要考慮一個問題,實例是否能夠保證是全局惟一,只要知足這個條件,這個單例設計的確定就合格了。多線程

關於實例是否能夠保證是全局惟一的延伸出的問題:jvm

  1. 是否線程安全,不安全確定就不能保證全局只有一個實例
  2. 是否支持序列化,支持序列化的類,被反序列化以後確定就不是全局惟一了
  3. 是否支持反射,支持反射確定也不是全局惟一的
  4. 是否能夠被克隆,這個也不能保證全局惟一

因此設計一個安全的單例須要考慮的問題仍是不少的。函數

針對上述問題常見的解決辦法:優化

  1. 保證線程安全,使用volatile+synchronized實現
  2. 防止序列化攻擊,重寫readResolve方法
  3. 防止反射,經常使用的方案是在單例類裏增長一個boolean類型的flag標識,在實例化的時候先判斷flag標識
  4. 防止克隆,重寫clone()方法

實現一個最簡單的單例就須要考慮到以上的全部問題,這個時候什麼有用的方法還沒寫那,代碼就已經不少了,那有沒有簡單的辦法既知足上述條件,代碼又簡潔那,那確定有,使用枚舉實現單例。網站

常見的單例模式設計方案

常見的單例模式設計方案大概有五種,懶漢模式,餓漢模式,雙重檢查方式實現,靜態內部類實現,枚舉實現。線程

簡單的分個類:設計

  1. 是否支持延遲加載,分爲懶漢模式和餓漢模式
  2. 線程安全設計了雙重檢查模式實現,靜態內部類實現方式
  3. 不支持序列化,反射,克隆,枚舉實現方式

其中懶漢模式,餓漢模式,雙重檢查方式實現,靜態內部類的實現方式均可以歸納爲如下兩步:

  1. 構造函數私有化,保證在外部沒法new對象
  2. 提供一個static方法獲取當前實例(不一樣方案,實現不一樣)

固然枚舉的實現方式最簡單,也最安全的,因此推薦使用枚舉實現,其次推薦使用靜態內部類方式實現。

餓漢模式

不是延遲加載,加載類的時候直接初始化

/**
 * @auther: chenmingyu
 * @date: 2019/2/12 16:26
 * @description:
 */
public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance(){
        return singleton;
    }
}

優勢:線程安全,代碼簡單。

缺點:不是延遲加載,若是你用不到這個類,它也會實例化,還有一個問題就是若是這個實例依賴外部一些配置文件,參數什麼的,在實例化以前就要獲取到,不然就實例化異常

懶漢模式

延遲加載,首次須要使用的時候在實例化,須要考慮線程安全

線程不安全的實現方式

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance(){
        if(null == singleton){
            singleton = new Singleton();
        }
        return singleton;
    }
}
雙重檢查(DCL:Double Check Lock)

線程安全的實現方式:

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {
    }

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

面試官:爲何使用volatile修飾singleton變量?

  1. 說的volatile,首先確定回答volatile的可見性
  2. 防止重排序優化,若是不用volatile修飾,多線程的狀況下,可能會出現線程A進入synchronized代碼塊,執行new Singleton();,首先給singleton分配內存,可是尚未初始化變量,這時候線程B進入getInstance方法,進行第一個判斷,此時singleton已經不爲空,直接返回singleton,而後確定報錯。使用volatile修飾以後禁止jvm重排序優化,因此就不會出現上面的問題
靜態內部類實現

使用靜態內部類實現也是延遲加載,利用靜態內部類去實現線程安全,只有在第一次調用getInstance方法的時候纔會去加載SingletonHolder,初始化SINGLETON

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance(){
        return SingletonHolder.SINGLETON;
    }

    private static class SingletonHolder{
        private static final Singleton SINGLETON = new Singleton();
    }
}
枚舉實現

枚舉實現代碼更簡潔,線程安全,而且保證枚舉不會被反序列化,反射和克隆

/**
 * @auther: chenmingyu
 * @date: 2019/2/12 16:30
 * @description:
 */
public enum Singleton {
    
    SINGLETON;

    /**
     * 提供的方法
     */
    public void method(){
        System.out.println("枚舉實現");
    }
}

因此推薦使用枚舉方式,調用Singleton.SINGLETON.method();

相關文章
相關標籤/搜索