1. DCL 的目的
Double Check Lock 是多線(xiàn)程環(huán)境下為提高延遲初始化效率而被廣泛使用的一種方式。我們常常會(huì)使用延遲初始化,以降低服務(wù)啟動(dòng)時(shí)間愉阎。
/**
* code 1.1
*/
@NotThreadSafe
public class Client {
private LazyInitClass instance ;
public LazyInitClass getInstance() {
if(instance == null)
instance = new LazyInitClass("LazyInitClassFieldName") ;
return instance ;
}
}
上面的代碼是典型的延遲初始化的例子。當(dāng)上面的例子暴露在多線(xiàn)程環(huán)境下時(shí),便會(huì)出現(xiàn)各種問(wèn)題筋蓖。最明顯的錯(cuò)誤:方法會(huì)返回多個(gè) LazyInitClass 對(duì)象。
/**
* code 1.2
*/
@NotThreadSafe
public class Client {
private LazyInitClass instance ;
public synchronized LazyInitClass getInstance() {
if(instance == null)
instance = new LazyInitClass("LazyInitClassFieldName") ;
return instance ;
}
}
上面的代碼在方法層面使用了 synchronized 關(guān)鍵字退敦,每次調(diào)用 getInstance 方法都進(jìn)行同步粘咖,的確可以有效避免多線(xiàn)程環(huán)境下多次調(diào)用 getInstance 得到不同的 LazyInitClass 對(duì)象。但當(dāng) instance 初始化完成后侈百,同步便沒(méi)有了意義瓮下。同步則成為影響 getInstance 性能的關(guān)鍵。有沒(méi)有一種方法钝域,可以在初始化時(shí)進(jìn)行正確的同步讽坏,初始化完成后又避免同步呢?于是 DCL 出現(xiàn)了例证。
/**
* 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 ;
}
}
很不幸路呜,上述代碼在編譯器優(yōu)化、多處理器共享內(nèi)存的情況下织咧,并不能正常工作胀葱。
LazyInitClass 代碼如下:
/**
* code 1.4
*/
@NotThreadSafe
public class LazyInitClass {
private String lazyInitClassField ;
public LazyInitClass(String lazyInitClassField) {
this.lazyInitClassField = lazyInitClassField ;
}
}
2. DCL 存在的問(wèn)題
LazyInitClass 實(shí)例寫(xiě)入 instance field,與 LazyInitClass 對(duì)象內(nèi)部 lazyInitClassField 對(duì)象的初始化兩步操作將會(huì)出現(xiàn)有序性問(wèn)題笙蒙。(詳細(xì)的有序性描述可以閱讀上一篇文章:《Java 并發(fā)系列(一):多線(xiàn)程三大特性》)
具體表現(xiàn)為:某一線(xiàn)程調(diào)用 getInstance 方法后抵屿,將得到一個(gè)非空的 instance 對(duì)象,但卻只能看到 lazyInitClassField 的默認(rèn)值捅位,即:lazyInitClassField 為空字符串轧葛,而非構(gòu)造方法中傳入的LazyInitClassFieldName。
3. 使 DCL 正常工作
3.1 JDK 1.3 以后(包含 JDK 1.3)的解決方案
/**
* 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);
}
}
3.2 JDK 1.5 以后(包含 JDK 1.5)的解決方案
從 JDK5 開(kāi)始艇搀,Java Memory Model 升級(jí)尿扯,volatile 關(guān)鍵字便可以保證可見(jiàn)性與有序性崖堤。
要使 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 ;
}
}
3.3 JDK 1.3 以前(不包含 JDK 1.3)的解決方案
由于 JDK1.2 版本爪瓜,ThreadLocal 非常慢鉴扫,所以 JDK 1.2 并不推薦使用 ThreadLocal 解決 DCL 問(wèn)題琼掠。所以 JDK1.3 版本以前混移,DCL 并沒(méi)有解決方案救拉。
3.4 不可變對(duì)象
/**
* code 3.4
*/
@ThreadSafe
public class ImmutableLazyInitClass {
private final String lazyInitClassField ;
public ImmutableLazyInitClass(String lazyInitClassField) {
this.lazyInitClassField = lazyInitClassField ;
}
}
如果 LazyInitClass 對(duì)象是不可變對(duì)象氏堤,則不使用 volatile 關(guān)鍵字 DCL 也能正常工作(code 1.3 所示)。這是由 Java 內(nèi)存模型中慢蜓,final 域的特殊語(yǔ)義保證的:final 域能確保初始化過(guò)程的安全性亚再,從而可以不受限制地訪(fǎng)問(wèn)不可變對(duì)象,并在共享這些對(duì)象時(shí)無(wú)須同步晨抡。
4. DCL 的替代方案
/**
* code 4.1
*/
@ThreadSafe
public class Client {
private static class LazyInitClassHolder {
static LazyInitClass singleton = new LazyInitClass("LazyInitClassFieldName");
}
public static LazyInitClass getInstance() {
return LazyInitClassHolder.singleton ;
}
}
這種方式被稱(chēng)為延遲初始化占位類(lèi)模式氛悬,由 Java 語(yǔ)義保證:只有調(diào)用了 getInstance 方法后,LazyInitClassHolder.singleton 才會(huì)被初始化耘柱。所以此方式能完美替代 DCL如捅。
5. 總結(jié)
DCL 的使用方式已經(jīng)被廣泛廢棄。DCL 之所以出現(xiàn)是因?yàn)闊o(wú)競(jìng)爭(zhēng)同步的執(zhí)行速度很慢调煎,以及 JVM 啟動(dòng)很慢镜遣。但這兩個(gè)問(wèn)題已經(jīng)不復(fù)存在,因而它并不是一種高效的優(yōu)化措施士袄。延遲初始化占位類(lèi)模式能帶來(lái)相同的優(yōu)勢(shì)悲关,并更容易理解。
6. 參考資料
《Performance of techniques for correctly implementing lazy initialization》
《Java Concurrency in Practice》作者:Brain Goetz娄柳、Tim Peierls寓辱、Joshua Bloch、Joseph Bowbeer赤拒、David Holmes秫筏、Doug Lea