不管你年底換不換工作,瞭解下單例模式

鍋外的大佬發表於2019-12-01

1. 單例模式

什麼是單例模式?簡言之就是確保定義為單例模式的類在程式中有且只有一個例項。單例模式的特點:

  1. 只有一個例項 (只能有一個物件被建立)
  2. 自我例項化(類構造器私有)
  3. 對外提供獲取例項的靜態方法

2.單例模式的實現

常見的單例模式實現方式有五種:

2.1. 懶漢式

懶漢式(一般也稱之為 飽漢式),具體程式碼實現如下:


public class Singleton {

    /**
     * 自我例項化
     */
    private static Singleton singleton;

    /**
     * 構造方法私有
     */
    private Singleton() {
        System.out.println("建立單例例項...");
    }

    /**
     * 對外提供獲取例項的靜態方法
     */
    public static Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }
}複製程式碼

從程式碼實現中可以看到,例項並不是在一開始就是初始化的,而是在呼叫 getInstance()方法後才會產生單例,這種模式延遲初始化例項,但它並非是執行緒安全的。

public class SingleTonTest {

    /**
     * 多執行緒模式下測試懶漢模式是否執行緒安全
     *
     * @param args
     */
    public static void main(String[] args) {
        /**
         * 這裡我圖方便,直接用Executors建立執行緒池
         * 阿里巴巴開發手冊是不推薦這麼做的
         */
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "::" + Singleton.getInstance()));
        }
    }
}複製程式碼

測試結果截圖:

懶漢式

懶漢式是在執行時載入物件的,所以載入該單例類時會比較快,但是獲取物件會比較慢。且這樣做是執行緒不安全的,如果想要執行緒安全,可以在getInstance()方法加上synchronized 關鍵詞修飾,但這樣會讓我們付出慘重的效率代價。

2.2. 餓漢式

提前建立好例項物件,呼叫效率高,但無法延時載入,容易產生垃圾,執行緒安全。

public class Singleton {

    /**
     * 自我例項化
     */
    private static Singleton singleton = new Singleton();

    /**
     * 構造方法私有
     */
    private Singleton() {
        System.out.println("建立單例例項...");
    }

    /**
     * 對外提供獲取例項的靜態方法
     */
    public static Singleton getInstance() {
        return singleton;
    }
}複製程式碼

2.3. 雙重檢查鎖模式

public class Singleton {

    /**
     * 自我例項化,volatile修飾,保證執行緒間可見
     */
    private volatile static Singleton singleton;

    /**
     * 構造方法私有
     */
    private Singleton() {
        System.out.println("建立單例例項...");
    }

    /**
     * 對外提供獲取例項的靜態方法
     */
    public static Singleton getInstance() {
        // 第一次檢查,避免不必要的例項
        if (singleton == null) {
            // 第二次檢查,同步,避免產生多執行緒的問題
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}複製程式碼

由於singleton=new Singleton()物件的建立在JVM中可能會進行重排序,在多執行緒訪問下存在風險,使用volatile修飾signleton例項變數,能禁止指令重排,使得物件在多執行緒間可見,能夠有效解決該問題。

雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺記憶體模型。記憶體模型允許所謂的“無序寫入”,這也是這些習語失敗的一個主要原因

2.4. 靜態內部類模式

public class Singleton {
    /**
     * 構造方法私有
     */
    private Singleton() {
        System.out.println("建立單例例項...");
    }

    private static class SingletonInner {
        private static Singleton instance = new Singleton();
    }

    private static Singleton getInstance() {
        return SingletonInner.singleton;
    }
}複製程式碼

這樣寫充分利用靜態內部類的特點——初始化操作和外部類是分開的,只有首次呼叫getInstance()方法時,虛擬機器才載入內部類(SingletonInner.class)並初始化instance, 保證物件的唯一性。

2.5. 列舉單例模式

public enum Singleton {
    INSTANCE 
}複製程式碼

感覺異常簡單,預設列舉類建立的物件都是單例的,且支援多執行緒。

3.單例模式總結

  1. 單例模式優點在於:全域性只會生成單個例項,所以能夠節省系統資源,減少效能開銷。然而也正是因為只有單個例項,導致該單例類職責過重,違背了“單一職責原則”,單例類也沒有抽象方法,會導致比較難以擴充套件。
  2. 以上所有單例模式中,推薦使用靜態內部類的實現,非常直觀,且保證執行緒安全。在《Effective Java》中推薦列舉類,但太簡單了,導致程式碼的可讀性比較差。
  3. 單例模式是建立型模式,反序列化時需要重寫readResovle()方法,以保證例項唯一。

文章首發於本人部落格:www.developlee.top, 轉載請註明出處!

關注公眾號,後臺回覆666, 領取福利:

liululee

相關文章