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#中不能使用這種方式。