延遲初始化,雙重檢查加鎖的陷阱

在初始化類時,有時爲了避免不必要的開銷,我們會採用延遲初始化的方式來構造類。在多線程的環境中,我們經常會使用雙重檢查加鎖的方式來初始化類,甚至在百度移動應用統計的源碼中,我就看到了許多使用這種延遲初始化的方式,如 程序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的構造函數會在子類構造函數運行之前先將默認值寫入所有的域,因此,某個域的默認值可能被視爲失效值)。從而調用了一個部分構造的對象,引發錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章