Java 單例模式

? ? 說到單例模式大家應該都不陌生,就是程序在運行過程中,一個類只允許有一個實例存在于內存當中。例如線程池管理類、緩存管理類、某個模塊內存管理類等。這些類之所以只允許一個實例,除了內存消耗方面的考慮外,還有程序正確性的考量。
? ? 例如,假設圖片內存管理類有兩個實例A和B,那在實例A和B上分別有兩個內存緩存,假設把一張圖片存放在A,去B拿的時候就拿到空了,顯然不合理。
? ? 單例模式要求一個類永遠只能返回同一個實例,實現的步驟有兩個:
? ? 1.將類的構造方法的修飾符改為private,這樣外部將無法實例化這個類;
? ? 2.該類對外提供一個獲取實例的方法,內部有一個指向本類的對象引用,當引用為空時,實例化這個類并返回,否則直接返回引用。
下面給出一些單例模式的實現:

1.餓漢模式:在類加載的時候直接實例化【可使用】
public class Singleton {

    private static volatile Singleton sInstance = new Singleton();

    private Singleton() {
    }

    public static Singleton instance() {
        return sInstance;
    }
}

優點:實現簡單,在類被加載時就實例化,避免了線程同步問題;
缺點:類加載時就實例化,沒有懶加載,后面可能用不到這個實例,造成了內存資源的浪費。

2.懶漢模式(同步方法)【不推薦】
public class Singleton {

    private static Singleton sInstance;

    private Singleton() {}

    public static synchronized Singleton instance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}

優點:懶加載,線程同步;
缺點:盡管JDK7對synchronized做了優化(偏向鎖、輕量級鎖、自旋鎖、鎖去除),但是即使對象已經實例化了,每次也都要進行同步操作,易造成堵塞,效率低。

3.懶漢模式(雙重檢查)【不推薦】
public class Singleton {

    private static volatile Singleton sInstance;
    private static Object mObject = new Object();

    private Singleton() {
    }

    public static Singleton instance() {
        if (sInstance == null) {
            synchronized (mObject) {
                if (sInstance == null) {
                    sInstance = new Singleton();
                }
            }
        }
        return sInstance;
    }
}

優點:兩次check null,中間加必要的同步,實現了線程安全,創建了對象。接下來當第一步判斷不為空的時候,就直接返回了,效率也比較高。
缺點:注意到sInstance前面使用volatile關鍵字修飾了嗎?
這里說下題外話,JVM在new一個對象的時候,有如下3個步驟:

  • a. 給Singleton的實例分配內存
  • b. 調用構造函數,初始化成員屬性字段
  • c. 將sInstance指向這塊內存
    首先這個過程不是原子操作,其次JVM允許指令重排,當執行的順序是a - b - c的時候是沒問題,但是當執行的順序是a - c - b時,線程A執行到c,讓出了CPU執行時間片,此時線程B判斷sInstance是不為空,直接返回引用,但是此時對象還沒創建,用的時候豈不是很尷尬?NPE很有可能即將隨之而來。這就是DCL失效問題,這種問題難以跟蹤、復現,出現的概率小。
    volatile能在sInstance插入內存屏障,防止指令重排,使每次讀取都得去主內存讀取,因此能防止這樣的問題。
    但指令重排什么的是JVM為程序運行做的優化之一,這樣子做不太好,并且每次都得去主內存讀取,或多或少也影響到性能。這種優化在《Java 高并發程序設計》等書籍上被稱為“丑陋的優化”,因此不推薦使用這種。
4. 靜態內部類【推薦】
public class Singleton {

    private Singleton() {
    }

    public static Singleton instance() {
        return SHolder.sInstance;
    }

    private static class SHolder {
        private static final Singleton sInstance = new Singleton();
    }
}

這個看著跟餓漢模式有幾分相似?是懶漢加載嗎?
類的靜態屬性只會在第一次加載類的時候初始化,因此sInstance只會在第一次調用SHolder.sInstance,即外部調用instance()時,才會初始化。
優點:巧妙利用類的初始化時機,避免了線程同步問題(類在初始化時,其它線程無法進入),同時實現了懶加載;

5. 枚舉【推薦】
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

借助JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象??赡苁且驗槊杜e在JDK1.5中才添加,所以在實際項目開發中,很少見人這么寫過。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容