變數的延遲初始化
阿新 • • 發佈:2018-12-05
在絕大多數的系統中,我們都會使用正常的初始化。正常的初始化程式碼是這樣的:
private final MyClass field = new MyCLass();
但在有些程式中,我們不希望某些變數在正常的類載入過程中就被初始化。換言之,我們希望某些變數能夠延遲初始化。在閱讀下面的內容之前,筆者希望你能讀一下我的另外一篇文章, 單例模式的5種JAVA實現。你要問我為什麼?答案只有四個字,“見多識廣”!本文要介紹的內容大多采用這篇部落格的技術方法。
說正題,如何實現延遲初始化呢?最開始想到的可能是醬紫:
private MyClass field;//正確的程式碼 public MyClass getField(){ synchronized (this){ if(field==null){ field=new MyClass(); } } return field; }
你肯定會問,怎麼變這麼複雜啦?下面這樣豈不是更好?
private MyClass field;//錯誤的程式碼
public MyClass getField() {
if (field == null) {
field = new MyClass();
}
return field;
}
上面是錯誤的程式碼,為何?想象下想在有兩個執行緒併發呼叫getField()方法,存在這樣的一個時序,可能讓兩個執行緒都執行了一遍new MyClass() 這個時候,到底隨的才算是合法的初始化過程呢?很顯然不對。那麼正常的初始化不存在這個問題嗎?恭喜你,問對地方了。答案是正常的初始化過程不存在這樣的問題。因為正常的變數初始化過程是在類的<clinit>方法中進行的,而<clinit>方法是被JVM同步執行的。是不是又漲姿勢了。具體的原理可以參見我的部落格
那麼問題就很明瞭了,既然人家正常的初始化流程需要同步執行,那麼延遲的初始化流程也需要被同步執行。區別僅僅在於前者的同步工作是JVM來完成,而後者的同步工作需要程式設計師自己完成。誰讓你非要延遲初始化呢!
言歸正傳,通過上面介紹的正確的程式碼,我們就可以實現延遲初始化了。但是這樣的程式碼併發度太低,因此我們使用雙重校驗鎖的方案。如下程式碼:
這是雙重校驗鎖模式的程式碼,為何這麼寫,還請讀者參照 單例模式的5種JAVA實現中的雙重校驗鎖模式,那裡面講的很明白,在此不贅述。private volatile MyClass field; public MyClass getField() { if(field==null) { synchronized (this) { if (field == null) { field = new MyClass(); } } } return field; }
當然我們也可以使用靜態內部類的方案來進行變數的延遲初始化。程式碼如下:
private static class Holder{
private static final MyClass field=new MyClass();
}
public static MyClass getField() {
return Holder.field;
}
好啦,介紹了3種方法來實現變數的延遲初始化。那麼哪種方案好呢?
簡言之,大多數的變數應該使用正常的初始化流程,而不是延遲初始化。當必須使用延遲初始化時,就可以使用對應的延遲初始化方法。對於例項域,我們使用雙重校驗鎖來實現效能更高。對於靜態域,我們使用靜態內部類來實現效能更高。
筆者開設了一個知乎live,詳細的介紹的JAVA從入門到精通該如何學,學什麼?
提供給想深入學習和提高JAVA能力的同學,歡迎收聽https://www.zhihu.com/lives/932192204248682496