在Java多線程程序中,有時候須要採用延遲初始化來下降初始化類和建立對象的開銷。安全
CASE:多線程
public class Singleton{ private static Singleton instance = null; private Singleton(){ // 將構造函數私有化 } public static Singleton getInstance(){ if(instance == null){ // 第一次檢查 synchronized (Singleton.class) { // 加鎖 if (instance == null) { // 第二次檢查 instance = new Singleton(); // 初始化instance對象 } } } return instance; } } 分析: 1)線程A執行instance = new Singleton();這行代碼能夠分解爲以下的3個步驟: ①類的加載、鏈接(驗證->準備->解析)。 ②初始化對象。 注:初始化後,類的加載就完成了。 ③將instance指向剛分配的內存地址。 注:這一步和類的加載過程沒有任何關係。 2)其中的②和③可能會被重排序: 分配對象的內存空間 --> 將instance指向剛分配的內存地址。(注意,此時對象尚未被初始化!) --> 初始化對象。 3)若是發生重排序,另外一個併發執行的線程B就有可能在第一次檢查時判斷instance不爲null,線程B接下來將訪問instance所引用的對象,但此時這個對象可能尚未被A線程初始化!
實現線程安全的延遲初始化的2個方法:併發
1)基於volatile的解決方案,不容許②和③重排序。 原理:當聲明對象的引用爲volatile後,②和③之間的重排序,在多線程環境中將會被禁止。 優勢:靜態字段、實例字段都可以實現延遲初始化。 public class Singleton{ private volatile static Singleton instance = null; private Singleton(){ // 將構造函數私有化 } public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class) { if (instance == null) { // instance是volatile變量,故instance初始化的時候沒有進行重排序。 instance = new Singleton(); } } } return instance; } } 2)基於類初始化的解決方案(使用靜態內置類),容許②和③重排序,但不容許其它線程「看到」這個重排序。 原理: [1]類在初始化時,必須先獲取到Class對象的初始化鎖,若是線程A已經獲取到了Class對象的初始化鎖,此時,其它線程因獲取不到初始化鎖,從而沒法對類進行初始化操做。 [2]類初始化的時機: 1>使用new關鍵字來實例化對象的時候 2>讀取或設置類的靜態字段(注:被final修飾、已在編譯期間把結果放入常量池的靜態字段除外)。 3>以及調用類的靜態方法時。 public class Singleton{ private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; // 觸發SingletonHandler類的初始化 } }