延遲初始化(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的原子性。
新聞熱點
疑難解答