標籤:單例 java
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) { // 邏輯代碼 }}
上述單例中,Singleton.INSTANCE就表示了一個單例。非常簡單高效。
注意Java中enum關鍵字和C#中的enum,差別很大,C#中不能使用這種方式。
本文出自 “一隻部落格” 部落格,請務必保留此出處http://cnn237111.blog.51cto.com/2359144/1641192
Java中如何?單例模式