歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java中如何實現單例模式

Java中如何實現單例模式

日期:2017/3/1 9:29:44   编辑:Linux編程

Java中,單例模式通常有2種分類餓漢模式和懶漢模式。
餓漢模式指的是單例實例在類裝載時就被創建了。
懶漢方式值的是單例實例在首次使用時才被創建。
無論是餓漢模式還是懶漢模式,都是用了一個靜態成員變量來存放真正的實例。並且私有化構造函數,防止被外部實例化。
單例(餓漢模式)代碼:
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) {
// 邏輯代碼
}
}

Copyright © Linux教程網 All Rights Reserved