Double Check Lock 是多線程環境下爲提升延遲初始化效率而被普遍使用的一種方式。咱們經常會使用延遲初始化,以下降服務啓動時間。html
/** * code 1.1 */ @NotThreadSafe public class Client { private LazyInitClass instance ; public LazyInitClass getInstance() { if(instance == null) instance = new LazyInitClass("LazyInitClassFieldName") ; return instance ; } }
代碼java
上面的代碼是典型的延遲初始化的例子。當上面的例子暴露在多線程環境下時,便會出現各類問題。最明顯的錯誤:方法會返回多個 LazyInitClass 對象。git
/** * code 1.2 */ @NotThreadSafe public class Client { private LazyInitClass instance ; public synchronized LazyInitClass getInstance() { if(instance == null) instance = new LazyInitClass("LazyInitClassFieldName") ; return instance ; } }
代碼github
上面的代碼在方法層面使用了 synchronized 關鍵字,每次調用 getInstance 方法都進行同步,的確能夠有效避免多線程環境下屢次調用 getInstance 獲得不一樣的 LazyInitClass 對象。但當 instance 初始化完成後,同步便沒有了意義。同步則成爲影響 getInstance 性能的關鍵。有沒有一種方法,能夠在初始化時進行正確的同步,初始化完成後又避免同步呢?因而 DCL 出現了。segmentfault
/** * code 1.3 */ @NotThreadSafe public class Client { private LazyInitClass instance ; public LazyInitClass getInstance() { if(instance == null){ synchronized(this){ if(instance == null){ instance = new LazyInitClass("LazyInitClassFieldName") ; } } } return instance ; } }
代碼安全
很不幸,上述代碼在編譯器優化、多處理器共享內存的狀況下,並不能正常工做。多線程
LazyInitClass 代碼以下:併發
/** * code 1.4 */ @NotThreadSafe public class LazyInitClass { private String lazyInitClassField ; public LazyInitClass(String lazyInitClassField) { this.lazyInitClassField = lazyInitClassField ; } }
代碼性能
LazyInitClass 實例寫入 instance field,與 LazyInitClass 對象內部 lazyInitClassField 對象的初始化兩步操做將會出現有序性問題。(詳細的有序性描述能夠閱讀上一篇文章:《Java 併發系列(一):多線程三大特性》)優化
具體表現爲:某一線程調用 getInstance 方法後,將獲得一個非空的 instance 對象,但卻只能看到 lazyInitClassField 的默認值,即:lazyInitClassField 爲空字符串,而非構造方法中傳入的LazyInitClassFieldName。
/** * code 3.1 */ @ThreadSafe class Client { private final ThreadLocal perThreadInstance = new ThreadLocal(); private LazyInitClass instance ; public LazyInitClass getInstance() { if (perThreadInstance.get() == null) createInstance(); return instance; } private void createInstance() { synchronized(this) { if (instance == null) instance = new LazyInitClass("LazyInitClassFieldName"); } perThreadInstance.set(perThreadInstance); } }
從 JDK5 開始,Java Memory Model 升級,volatile 關鍵字即可以保證可見性與有序性。
要使 DCL 正常工做,多了一種更爲方便的解決方案:
/** * code 3.2 */ @ThreadSafe public class Client { private volatile LazyInitClass instance ; public LazyInitClass getInstance() { if(instance == null){ synchronized(this){ if(instance == null){ instance = new LazyInitClass("LazyInitClassFieldName") ; } } } return instance ; } }
因爲 JDK1.2 版本,ThreadLocal 很是慢,因此 JDK 1.2 並不推薦使用 ThreadLocal 解決 DCL 問題。因此 JDK1.3 版本之前,DCL 並無解決方案。
/** * code 3.4 */ @ThreadSafe public class ImmutableLazyInitClass { private final String lazyInitClassField ; public ImmutableLazyInitClass(String lazyInitClassField) { this.lazyInitClassField = lazyInitClassField ; } }
若是 LazyInitClass 對象是不可變對象,則不使用 volatile 關鍵字 DCL 也能正常工做(code 1.3 所示)。這是由 Java 內存模型中,final 域的特殊語義保證的:final 域能確保初始化過程的安全性,從而能夠不受限制地訪問不可變對象,並在共享這些對象時無須同步。
/** * code 4.1 */ @ThreadSafe public class Client { private static class LazyInitClassHolder { static LazyInitClass singleton = new LazyInitClass("LazyInitClassFieldName"); } public static LazyInitClass getInstance() { return LazyInitClassHolder.singleton ; } }
這種方式被稱爲延遲初始化佔位類模式,由 Java 語義保證:只有調用了 getInstance 方法後,LazyInitClassHolder.singleton 纔會被初始化。因此此方式能完美替代 DCL。
DCL 的使用方式已經被普遍廢棄。DCL 之因此出現是由於無競爭同步的執行速度很慢,以及 JVM 啓動很慢。但這兩個問題已經不復存在,於是它並非一種高效的優化措施。延遲初始化佔位類模式能帶來相同的優點,並更容易理解。