Java中如何實現單例模式

Java中,單例模式一般有2種分類餓漢模式和懶漢模式。java

餓漢模式指的是單例實例在類裝載時就被建立了。緩存

懶漢方式值的是單例實例在首次使用時才被建立。安全

不管是餓漢模式仍是懶漢模式,都是用了一個靜態成員變量來存放真正的實例。而且私有化構造函數,防止被外部實例化。多線程

單例(餓漢模式)代碼:ide

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    //私有化構造方法,防止被實例化   
    private Singleton() {
    }

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


單例懶漢模式代碼,注意靜態字段聲明的時候,有一個volatile關鍵字,而且代碼中兩次判斷是不是null,這種雙重檢測的機制爲了應對多線程環境。函數

public class Singleton {
    private static volatile Singleton INSTANCE = null;

    //私有化構造方法,防止被實例化
    private Singleton() {
    }

    //雙重檢測
    public static Singleton getInstance() {
        if (INSTANCE == null) { //①

            synchronized (Singleton.class) { //②

                if (INSTANCE == null) { //③
                    INSTANCE = new Singleton(); //④
                }
            }
        }

        return INSTANCE;
    }
}


須要注意的是,即便這種Doublecheck在C++中有效,但對JAVA5以前的代碼仍是有一點問題的。優化

緣由在於,編譯器會優化代碼,可能致使賦值語句亂序執行,上述代碼中,若是有2個線程A,B。A線程已經進入4位置,當4位置的代碼執行時,須要注意 INSTANCE = new Singleton()這個行代碼不是一個原子操做。線程

JVM可能在完成Singleton類的構造方法以前,會先把一塊還未初始化完成的內存地址先分配給INSTANCE,而此時若是線程B進入1位置,會認爲INSTANCE已經存在,從而返回了一個未初始化完成的內存塊,這可能致使程序崩潰。正是因爲是先給INSTANCE賦值在初始化內存塊,仍是先初始化內存塊再複製給INSTANCE,這個順序沒法保證,因此這種機制會出現問。因此在JAVA5以後,擴充了 volatile關鍵字,確保一個變量寫入和讀取操做的順其不會被編譯器優化成亂序,volatile變量也不會被緩存到cpu寄存器中,保證了其讀取的一致性。接口

這和C#中的volatile的關鍵字是同樣的做用。內存

除了上面2中常見的方法以外,還有其餘方法。好比下面一種比較經典的實現方法,使用一個內部類,JVM自身保證了自身安全,這個模式也是在《Effective Java》這本書中推薦的,這種方式不依賴於java版本。

public class Singleton {
    private Singleton() {
    }

    public static final Singleton getInstance() {
        return InnerClass.INSTANCE;
    }

    private static class InnerClass {
        private static Singleton INSTANCE = new Singleton();
    }
}


但在JAVA5以後,最簡單的單例實現方法是使用enum類型。

enum關鍵字是JAVA5中新增的,它和class,interface同樣,也是一種數據類型。能夠把它當作是一種特殊的類。能夠在enum內部實現構造方法,字段,方法等,還能夠實現接口。不過也有一些限定,枚舉類中的構造器,默認爲private修飾,且只能使用private。枚舉類的全部實例必須在類中的第一行顯式列出,不然這個枚舉類不可能產生實例。JVM保證了這個每一個枚舉值只被初始化一次。正是因爲這樣一個特色,咱們能夠用以下代碼能夠實現單例。

public enum Singleton {INSTANCE;
    public void dosth(String arg) {
        // 邏輯代碼
    }
}


上述單例中,Singleton.INSTANCE就表示了一個單例。很是簡單高效。

注意Java中enum關鍵字和C#中的enum,差異很大,C#中不能使用這種方式。

相關文章
相關標籤/搜索