Effective Java 讀書筆記——71:慎用延遲初始化

部分內容參考:

http://blog.csdn.net/fgakjfd/article/details/5282646


延遲初始化

延遲初始化(Lazy Initialization)是延遲到需要域的值時纔將它初始化的行爲,簡單來說,就是我們很常見的使用一個條件語句,來判斷對象是否爲空,如果爲空則初始化返回,如果不爲空,則直接返回。使用這種方式,對於特定對象,只初始化了一次。
不過,對於延遲初始化,建議“除非絕對必要,否則就不要這麼做”。下面介紹幾種實現延遲初始化的方法:


普通模式


下面是正常初始化的一個典型聲明,注意final修飾符:
private final FieldType field = computeFieldValue();

如果利用延遲初始化,會破壞普通的初始化循環,因此需要保證同步,首先使用最簡單的synchronized來保證同步:
	private FieldType field;

	public synchronized FieldType getField() {
		if (field == null) {
			field = computeFieldValue();
		}
		return field;
	}

上面兩種方式用到靜態域類似的,加上static關鍵字修飾即可。


下面介紹另一種延遲初始化的模式。

lazy initialization holder class 模式

如果從性能角度考慮,需要對靜態域進行延遲初始化,可以使用這種模式。
	private static class FieldHolder {
		static final FieldType field = computeFieldValue();
	}

	public static FieldType getField() {
		return FieldHolder.field;
	}
當調用getField方法時,第一次讀取FieldHolder.field,導致靜態內部類進行初始化。這種模式的關鍵在於,getField方法不需要同步,並且只執行一個域的訪問,因此並沒有增加更多的成本。這裏實際上利用了靜態內部類在使用的時候才進行初始化這個特點。

靜態內部類

如果你不需要內部類對象與其外圍類對象之間有聯繫,那你可以將內部類聲明爲static。這通常稱爲嵌套類(nested class)。Static Nested Class是被聲明爲靜態(static)的內部類,它可以不依賴於外部類實例被實例化。而通常的內部類需要在外部類實例化後才能實例化。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類對象隱含地保存了一個引用,指向創建它的外圍類對象。然而,當內部類是static的時,就不是這樣了。嵌套類意味着: 

1. 嵌套類的對象,並不需要其外圍類的對象。 

2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。 

另外,需要注意的是,靜態內部類只有在第一次使用的時候纔會被加載。



雙重檢查模式

如果從性能角度考慮,需要對實例域進行延遲初始化,可以使用這種模式。這種模式避免了在初始化之後,再次訪問這個域時的鎖定開銷(在普通的方法裏面,會使用synchronized對方法進行同步,每次訪問方法的時候都要進行鎖定)。

這種模式的思想是:兩次檢查域的值,第一次檢查時不鎖定,看看其是否初始化;第二次檢查時鎖定。只用當第二次檢查時,表明其沒有被初始化,纔會調用computeFieldValue方法對其進行初始化。如果已經被初始化了,就不會鎖定了,另外該域被聲明爲volatile非常重要

	private volatile FieldType field;

	public FieldType getField() {
		FieldType result = field;
		if (result == null) {
			synchronized (this) {
				result = field;
				if (result == null) {
					field = result = computeFieldValue();
				}
			}
		}
		return result;
	}

在上面的代碼中,事實上,只要該域被初始化以後,無論如何再也不會進入第二次的條件語句判斷,也就是說被初始化以後,訪問的時候再也不會被synchronized鎖定。

另外值得注意的是,result局部變量的使用,是爲了保證在已經被初始化的情況下,原來的變量只被讀取一次到局部變量result中,否則在比較的時候需要讀取一次,返回的時候還需要讀取一次。

最後,對於實例域,就使用雙重檢查模式;對於靜態域,就使用lazy initialization holder class idiom。對於可以重複初始化的實例域,可以使用單重檢查模式(省去第二次檢查,此時或許不需要嚴格同步)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章