無論你年末換不換工做,瞭解下單例模式

1. 單例模式

什麼是單例模式?簡言之就是確保定義爲單例模式的類在程序中有且只有一個實例。單例模式的特色:java

  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()方法後纔會產生單例,這種模式延遲初始化實例,但它並不是是線程安全的。code

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 關鍵詞修飾,但這樣會讓咱們付出慘重的效率代價。blog

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

相關文章
相關標籤/搜索