23種設計模式之單例模式


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

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

什么是單例模式

單例模式目的是保證在程序運行期間一個類只有一個實例,並提供一個全局訪問點,無論什么情況下,只會生成一個實例,免去繁瑣的創建銷毀對象的過程。

如何設計單例

如何設計單例模式其實很簡單,只需要考慮一個問題,實例是否可以保證是全局唯一,只要滿足這個條件,這個單例設計的肯定就合格了。

關於實例是否可以保證是全局唯一的延伸出的問題:

  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();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM