Java 雙檢鎖問題__Java

來源:互聯網
上載者:User

來源:《The "Double-Checked Locking is Broken" Declaration》

  1. 單例模式的簡單實現

// 只支援單線程的版本class Foo {    private Helper helper = null;    public Helper getHelper() {        if (helper == null)             helper = new Helper();        return helper;    }}

在多線程的情況下,可能會產生多個Helper執行個體。

 

  2. 同步getHelper方法

// 支援多線程的版本class Foo {    private Helper helper = null;    public synchronized Helper getHelper() {        if (helper == null)             helper = new Helper();        return helper;    }}

 getHelper()方法被標記為synchronized後,JVM會只允許同時只有一個線程能執行getHelper方法。所以不會產生多個Helper執行個體。但是同步的開銷會導致多線程執行效率降低。

 

  3. 一種錯誤的雙檢鎖方法

// 一種錯誤的雙檢鎖方法class Foo {    private Helper helper = null;    public Helper getHelper() {        if (helper == null)             synchronized(this) {                if (helper == null)                     helper = new Helper();            }        return helper;    }}

 為什麼是錯的。

原因:在 helper = new Helper(); 這句中有兩個主要操作。一是建立Helper的一個執行個體,二是將這個新建立的Helper執行個體的引用賦給helper這個欄位。編譯器規定這兩個操作的順序可能是先賦值執行個體的引用,後執行Helper執行個體內部的初始化構建。這可能導致另一線程在調用getHelper()方法時,因為helper欄位不為null,所以開始使用helper所指的執行個體,而該執行個體的初始化工作卻仍在前一線程中處於未完成狀態,這就導致第二個線程使用了一個“壞”的Helper。

即使編譯器事先規定了先構建執行個體後賦值,在一個多處理器系統中,處理器和記憶體系統還是有可能顛倒這兩個操作的順序。

  4. 另一種錯誤的雙檢鎖方法

// 另一種錯誤的雙檢鎖方法class Foo {    private Helper helper = null;wei    public Helper getHelper() {        if (helper == null) {            Helper h;            synchronized(this) {                h = helper;                if (h == null)                     synchronized (this) {                        h = new Helper();                    }  // 釋放內部的 synchronized 鎖                helper = h;            }        }        return helper;    }}

 為什麼是錯的。

原因:退出監控(monitorexit)(如釋放 sychronized 鎖)的規則是“在 monitorexit 前的操作必須在釋放鎖之前執行”。但是沒有規則保證“在 monitorexit 後的操作必須在釋放鎖之後才能執行”。也就是說編譯器可能會把 helper = h; 這句移到內部的 synchronized 代碼塊內。這就又回到了3中的情況,即其它線程可能拿到一個“壞”的未完工的Helper執行個體。

註:在.Net CLR中情況有所不同。在CLR中,任何鎖方法的調用都構成了一個完整的記憶體柵欄,在柵欄之前寫入的任何變數都必須在柵欄之前完成;在柵欄之後的任何變數讀取都必須在柵欄之後開始。

 

同步用的越多,越有可能導致效能問題,也增大了出錯的可能。

另外每個處理器都緩衝了的變數值的備份,在某些類型的處理器中,即使其它處理器利用記憶體柵欄(memory barriers)將新值寫入了共用記憶體,因為處理器用的是它自己的備份值,還是會認為helper值為null,導致建立了Helper執行個體,並使用了這個新執行個體。(Alpha處理器)

  5. 可以對32位的原始類型資料變數用雙檢鎖(如int和float)

因為原始類型資料的變數存的就是值本身,所以對它賦值就直接改了變數內容。但是64位的原始類型資料變數,如long和double,就無法保證該操作的原子性。

class Foo {     private int cachedHashCode = 0;    public int hashCode() {        int h = cachedHashCode;        if (h == 0)             synchronized(this) {                if (cachedHashCode != 0) return cachedHashCode;                h = computeHashCode();                cachedHashCode = h;            }        return h;    }}

  6. 利用執行緒區域儲存的雙檢鎖方法

class Foo {    // 如果 perThreadInstance.get() 返回非null的值,說明已經有線程執行過該方法,helper已經被初始化好了    private final ThreadLocal perThreadInstance = new ThreadLocal();    private Helper helper = null;    public Helper getHelper() {        if (perThreadInstance.get() == null) createHelper();        return helper;    }    private final void createHelper() {        synchronized(this) {            if (helper == null)                helper = new Helper();        }        // 任何非空的值都可作為set的參數        perThreadInstance.set(perThreadInstance);    }}

該方法的效率取決於JDK中ThreadLocal的實現。

  7. 利用volatile的雙檢鎖方法

從JDK5開始,volatile嚴格地限制了變數的讀寫順序,不允許重排。

class Foo {    private volatile Helper helper = null;    public Helper getHelper() {        if (helper == null) {            synchronized(this) {                if (helper == null)                    helper = new Helper();            }        }        return helper;    }}

對不可變對象(Immutable Objects)引用的讀寫都是原子性的操作。所以如果Helper是不可變對象,如String和Integer,可以不用volatile關鍵字。

  8. 總結:

盡量參考使用已有的最佳實務,不要自己去發明。

一門合適的程式設計語言應該是優雅的。如果發現已有的問題很難用優雅的方式解決,要麼換一種語言,要麼發出聲音,讓開發這門語言的人從源頭改進該語言。

對於絕大多數只是使用語言的你,不要為了研究語言而研究,應該基於現有的業務問題去研究。多接觸各種業務,自然就會多遇到各種問題,自然有機會學得更多。

 

《C# 單例模式整理》

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.