採用延遲初始化來下降初始化類和建立對象的開銷

在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類的初始化
        }  
      
    }
相關文章
相關標籤/搜索