一、 問題分析
雙重檢查鎖(Double-checked Locking)可以降低直接使用synchronized同步共享資源帶來的性能開銷,使用DCL實現延遲加載的代碼如下:
public class DoubleCheckedLocking {
private static Instance instance;
public static Instance getInstance() {
if (null == instance) {
synchronized(DoubleCheckedLocking.class) {
if (null == instance)
instance = new Instance();
}
}
return instance;
}
}
DCL代碼先檢查共享變量是否爲null,若不爲null,則不需要執行加鎖和初始化操作,這樣就可以大幅降低synchronized 帶來的性能開銷。
但是這段代碼會存在訪問到仍爲初始化完成的對象,問題就在instance = new Instance();這句創建實例的代碼。這句代碼實際分爲3個步驟:
- 分配對象的內存空間
- 初始化對象
- 設置instance 變量指向分配的內存地址
根據Java SE 7 規範,所有線程在執行Java程序時必須遵循intra-thread semantics, 該規範保證重排序不會改變單線程的程序執行結果。
上述步驟中即使2、3之間的重排序,但在變單線程下,執行結果不會改變,即不會違反intra-thread semantics 規範。而且,這種重排序在沒有改變單線程執行結果的前提下,因爲並沒有一開始就初始化對象,可以提高程序的執行性能。
在多線程情況下,當A線程中2、3之間重排序後,A線程執行到第3步,還未初始化對象,此時B線程服務getInstance()方法,判斷instance不爲null返回對象,B線程將訪問到一個還未被初始化的對象。這就是雙重檢查鎖存在問題的根源。
二、 解決方案
可以通過兩個思路來實現線程安全的延遲初始化:
1) 禁止步驟2、3重排序
2) 允許重排序,但其他線程看不到這個重排序過程
2.1 基於volatile的解決方案
對於思路1,很容易想到volatile關鍵字,當用volatile修飾變量後,在多線程環境下2、3 步驟會被禁止重排序,使用volatile的方案如下:
public class DoubleCheckedLocking {
private static volatile Instance instance; // 共享變量使用volatile關鍵字修飾
public static Instance getInstance() {
if (null == instance) {
synchronized(DoubleCheckedLocking.class) {
if (null == instance)
instance = new Instance();
}
}
return instance;
}
}
注:基於volatile的解決方案需要JDK5 及以上的版本,因爲Java從JDK 5 開始使用新的JSR-133內存模型規範,增強了volatile語義:嚴格限制編譯器和處理器對volatile變量與普通變量的重排序,確保volatile的寫-讀和鎖的獲取具有相同的內存語義,此時volatile相當於輕量級鎖(詳見Java併發編程的藝術-volatile的內存語義))。
2.2 基於類初始化的解決方案
JVM 在類的初始化階段,即類被加載後,被線程使用之前,會進行類的初始化,此時JVM會獲取一個對象的初始化鎖。這個鎖可以同步多個線程對同一個類的初始化。
基於該特性,可以實現第2種解決方案:
public class InstanceFactory {
private static class InstanceHolder {
// 類中聲明的一個靜態字段被賦值
public static Instance instance = new Instance();
}
public static Instance getInstance() {
// InstanceHolder 類中聲明的靜態字段被使用,且該字段非常量字段,InstanceHolder 類
// 將立即初始化
return InstanceHolder.instance;
}
}
在多線程環境下,多個線程可能在同一時間去初始化同一個類或接口。
而 Java 語言規範規定,對於每個類C,都有一個唯一的初始化鎖LC與之對應(具體的映射由JVM實現)。JVM 在類初始化期間會獲取這個初始化鎖,且每個線程至少獲取一次鎖來確保這個類已經被初始化。
類的初始化示意圖如下:
參考:
1.《Java併發編程的藝術》,方騰飛,魏鵬,程曉明著。