Java多線程並發中的雙重檢查鎖定與延遲初始化__java

來源:互聯網
上載者:User
雙重檢查鎖定與延遲初始化

在Java多線程程式中,有時候需要採用延遲初始化來降低初始化類和建立對象的開銷。雙重檢查鎖定是常見的延遲初始化技術。
下面我們看一個非安全執行緒的延遲初始化對象的例子:

public class Singleton {    private static Singleton instance;    public static Singleton getInstance() {        if (instance == null) // 1:A線程執行            instance = new Singleton(); // 2:B線程執行        return instance;    }}

假設A線程執行代碼1的同時,B線程執行代碼2。此時,線程A可能會看到instance引用的對象還沒有完成初始化
下面我們對上面的代碼改造一下,讓他變成安全執行緒

public class Singleton {    private static Singleton instance;    public static synchronized Singleton getInstance() {        if (instance == null) // 1:A線程執行            instance = new Singleton(); // 2:B線程執行        return instance;    }}

由於對getInstance()方法做了同步處理,synchronized將導致效能開銷。如果getInstance()方法被多個線程頻繁的調用,將會導致程式執行效能的下降(一般情況在我們項目中提供的單例總是被頻繁的調用)。反之,如果getInstance()方法不會被多個線程頻繁的調用,那麼這個延遲初始化方案將能提供令人滿意的效能。
下面我們進一步通過雙重檢查鎖定來降低同步的開銷,代碼如下:

public class Singleton {    private static Singleton instance;    public static Singleton getInstance() {        // 第一次檢查        if(instance == null){            // 加鎖            synchronized(Singleton.class){                if (instance == null)                     //分配記憶體空間、初始化對象、instance指向分配的記憶體位址                    instance = new Singleton();             }        }        return instance;    }}

上面的代碼真的能保證單例嗎。讓我們來分析下
如上面代碼所示,如果第一次檢查instance不為null,那麼就不需要執行下面的加鎖和初始化操作。因此,可以大幅降低synchronized帶來的效能開銷。上面代碼錶面上看起來,似乎兩全其美。多個線程試圖在同一時間建立對象時,會通過加鎖來保證只有一個線程能建立對象。在對象建立好之後,執行getInstance()方法將不需要擷取鎖,直接返回已建立好的對象。雙重檢查鎖定看起來似乎很完美,但這是一個錯誤的最佳化。在代碼讀取到instance不為null時,instance引用的對象有可能還沒有完成初始化。
讓我們來繼續分析
前面的雙重檢查鎖定範例程式碼(instance=new Singleton();)建立了一個對象。這一行代碼可以分解為如下的3行虛擬碼。

memory = allocate();  // 1:指派至的記憶體空間ctorInstance(memory); // 2:初始化對象instance = memory;  // 3:設定instance指向剛分配的記憶體位址

面3行虛擬碼中的2和3之間,可能會被重排序2和3之間重排序之後的執行時序如下。

memory = allocate();  // 1:指派至的記憶體空間instance = memory;  // 3:設定instance指向剛分配的記憶體位址;注意,此時對象還沒有被初始化。ctorInstance(memory); // 2:初始化對象

下面讓我們看一下多線程並發的執行情況


由於單線程內要遵守intra-thread semantics,從而能保證A線程的執行結果不會被改變。但是,當線程A和B按圖3-38的時序執行時,B線程將看到一個還沒有被初始化的對象。
基於上面的問題現象我們有2種解決方案
1、不允許2和3重排序
2、允許2和3重排序,但是不允許對其他線程“看到”這個重排序
方案一:基於volatile解決方案

只需要前面基於雙重檢查鎖定來實現的延遲方案,把instance改成volatile(JDK1.5以上支援)

public class Singleton {    private static volatile Singleton instance;    public static Singleton getInstance() {        // 第一次檢查        if(instance == null){            // 加鎖            synchronized(Singleton.class){                if (instance == null)                     //分配記憶體空間、初始化對象、instance指向分配的記憶體位址                    instance = new Singleton();             }        }        return instance;    }}

當聲明為volatile以後2和3重排將被禁止,代碼將按照如下順序執行執行

方案二:基於類初始化的解決方案
JVM在類的初始化階段(即在Class被載入後,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去擷取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。基於這個特性,可以實現另一種安全執行緒的延遲初始化方案

public class Singleton {    private static class InstanceHolder {        public static Singleton instance = new Singleton();    }    public static Singleton getInstance() {        // 這裡將導致InstanceHolder類被初始化        return InstanceHolder.instance;    }}

假設兩個線程並發執行getInstance()方法,下面是執行的示意圖

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.