在初始化類時,有時爲了避免不必要的開銷,我們會採用延遲初始化的方式來構造類。在多線程的環境中,我們經常會使用雙重檢查加鎖的方式來初始化類,甚至在百度移動應用統計的源碼中,我就看到了許多使用這種延遲初始化的方式,如 程序1-1
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if(resource == null) {
synchronized(DoubleCheckedLocking.class) {
if(resource == null) {
resource = new Resource();
}
}
}
return resource;
}
}
程序 1-1
這樣的代碼是存在問題的,對象在沒有同步的狀態下缺少了Happens-Before的關係,可能出現重排序的問題。
初始化一個新的對象時需要寫入多個變量,即新對象中的各個域,同樣,在發佈一個引用時也需要寫入一個變量,即新對象的引用。如果新對象引用的寫入操作與對象中各個域的寫入操作重排序,如程序1-1,當A線程調用該方法後,這時候resource可能非空,因此B線程在在調用沒有同步的判斷代碼時將直接返回resource,但是這時可能看到Resource實例還未構造完成,因此可能看到某些或全部狀態中包含的是無效值(儘管在構造函數中設置的域值似乎是第一次向這些域中寫入的值,因此不會有“更舊的”值被視爲失效值,但Object的構造函數會在子類構造函數運行之前先將默認值寫入所有的域,因此,某個域的默認值可能被視爲失效值)。從而調用了一個部分構造的對象,引發錯誤。