JVM中,單例對象只有一個實例存在。java
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
最簡單的實現方式,可是若是對象的構造耗費時間,可能採用懶漢式更好。安全
public class Singleton { private static Singleton instance = null; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
也是很簡單粗暴的懶漢式實現方式,每次獲取單例的時候都須要獲取排他鎖,效率差。多線程
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; } }
指令重排序是JVM爲了優化指令,提升程序運行效率,在不影響單線程程序執行結果的前提下,儘量地提升並行度。
也就是說,JVM爲了執行效率會將指令進行從新排序,可是這種從新排序不會對單線程程序產生影響。優化
因爲instance = new Singleton();
操做並非一個原子性指令,會被分爲多個指令:ui
memory = allocate(); //1:分配對象的內存空間 ctorInstance(memory); //2:初始化對象 instance = memory; //3:設置instance指向剛分配的內存地址
可是通過重排序後以下:.net
memory = allocate(); //1:分配對象的內存空間 instance = memory; //3:設置instance指向剛分配的內存地址,此時對象還沒被初始化 ctorInstance(memory); //2:初始化對象
instance須要加volatile關鍵字,不然會出現錯誤。問題的緣由在於JVM指令重排優化的存在。在某個線程建立單例對象時,在構造方法被調用以前,就爲該對象分配了內存空間(空白內存)並將分配的內存地址賦值給instance字段了,然而該對象可能尚未初始化。若緊接着另一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。線程
public class Singleton { private static class SingletonHolder { private static Singleton instance = new Singleton(); private SingletonHolder(){ } } public static Singleton getInstance() { return SingletonHolder.instance; } }
內部靜態類是要在有引用了之後纔會裝載到內存的,因此在你第一次調用getInstance()以前,SingletonHolder是沒有被裝載進來的,只有在你第一次調用了getInstance()以後,裏面涉及到了return SingletonHolder.instance; 產生了對SingletonHolder的引用,內部靜態類的實例纔會真正裝載。這也就是懶加載的意思。code
遇到new、getstatic、putstatic、或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化,生成這4條指令的最多見的Java代碼場景是:使用new關鍵字實例化對象的時候,讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。對象
虛擬機會保證一個類的