延遲初始化(lazy initialization),也就是在真正被使用的時候才開始初始化的技巧。
不論是靜態還是實例,都可以進行延遲初始化。
其本質是初始化開銷和訪問開銷之間的權衡。
畢竟是一種優化技巧,使用不當會起反效果。
尤其是在多線程場景中這種反效果會尤為明顯,因為我們要對這個進行延遲初始化的field進行同步。
先一步步開始,如果初始化開銷不值一提,我們只需要保證其不可變即可:
private final FieldType field1 = computeFieldValue();
如果還有的商量,初始化開銷可能讓人在意,下面是最簡單的的方式,直接在訪問方法聲明裡加了synchoronized修飾,這種方式將訪問開銷最大化了:
private FieldType field2;
synchronized FieldType getField2() {
if (field2 == null)
field2 = computeFieldValue();
return field2;
}
private static FieldType computeFieldValue() {
return new FieldType();
}
如果要改為靜態的也不過是加上static修飾,但對於靜態初始化,我們可以使用class holder方式:
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldType getField3() {
return FieldHolder.field;
}
private static FieldType computeFieldValue() {
return new FieldType();
}
這種方式感覺不錯,我們沒有進行額外的同步處理,只有在訪問getField3的時候FieldHolder才會被初始化。
所以這種情況屬於沒有增加訪問開銷也保證了延遲特性。
這次試試優化一下實例field的訪問開銷,最經典的就是double-check了,這個東西經常出現在筆試題中:
private volatile FieldType field4;
FieldType getField4() {
FieldType result = field4;
if (result == null) {
synchronized (this) {
result = field4;
if (result == null)
field4 = result = computeFieldValue();
}
}
return result;
}
private static FieldType computeFieldValue() {
return new FieldType();
}
代碼中使用了result局部變量,這樣做雖然不是必要的,但這樣可以確保field已被初始化的情況下被讀取一次,可以提高少許效率。
以上就是延遲初始化的一些常用方式。
延遲初始化看起來不錯,但建議權衡訪問和創建的開銷,對於實例field使用double-check,對於靜態field使用holder class,以在多線程訪問時保證check-then-action的原子性。