簡體   English   中英

為什么在Java中雙重檢查鎖定被破壞?

[英]Why is double-checked locking broken in Java?

此問題涉及舊Java版本的行為和雙重檢查鎖定算法的舊實現

較新的實現使用volatile並依賴於稍微改變的volatile語義,因此它們不會被破壞。


據說,除了long或double字段外,字段賦值總是原子的。

但是,當我讀到為什么雙重檢查鎖定被破壞的解釋時,它說問題在於賦值操作:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    // other functions and members...
}
  1. 線程A注意到該值未初始化,因此它獲得鎖定並開始初始化該值。
  2. 由於某些編程語言的語義,允許編譯器生成的代碼在A完成初始化之前更新共享變量以指向部分構造的對象。
  3. 線程B注意到共享變量已初始化(或顯示),並返回其值。 因為線程B認為該值已經初始化,所以它不會獲得鎖定。 如果B在B看到A完成的所有初始化之前使用該對象(因為A尚未完成初始化或者因為對象中的某些初始化值尚未滲透到內存B使用(緩存一致性)) ,該程序可能會崩潰。
    (來自http://en.wikipedia.org/wiki/Double-checked_locking )。

什么時候可能? 是否有可能在64位JVM分配操作中不是原子的? 如果不是那么“雙重檢查鎖定”是否真的被打破了?

問題不在於原子性,而在於它的排序。 只要不違反發生之前的情況 ,JVM就可以重新排序指令以提高性能。 因此,運行時理論上可以在執行類Helper的構造函數的所有指令之前調度更新helper程序的指令。

引用的賦值是原子的,但構造不是! 因此,如解釋中所述,假設線程B想要在線程A完全構造它之前使用單例,它不能創建新實例,因為引用不是null,因此它只返回部分構造的對象。

如果您不確保在另一個線程加載該共享引用之前發布共享引用,則可以通過寫入其字段來重新排序對新對象的引用的寫入。 在這種情況下,另一個線程可以看到對象引用的最新值,但是對象的某些或全部狀態的過期值 - 部分構造的對象。 - Brian Goetz:Java Concurrency in Practice

由於初始檢查null未同步,因此沒有發布,並且可以進行此重新排序。

可能需要在構造函數中構造Helper實例的幾個賦值,並且語義允許它們相對於賦值helper = new Helper()重新排序。

因此,可以為字段helper分配對未進行所有分配的對象的引用,以使其未完全初始化。

在java中雙重檢查鎖定有各種各樣的問題:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

閱讀這篇文章: http//www.javaworld.com/jw-02-2001/jw-0209-double.html即使你不理解所有細節(像我一樣),只要相信這個好方法不起作用。

對不起,這可能與這個問題有點無關,我只是好奇。 在這種情況下,在分配之前獲取鎖和/或返回值是不是更好? 喜歡:

private Lock mLock = new ReentrantLock();
private Helper mHelper = null;

private Helper getHelper() {
    mLock.lock();
    try {
        if (mHelper == null) {
            mHelper = new Helper();
        }
        return mHelper;
    }
    finally {
        mLock.unlock();
    }
}

或者使用雙重檢查鎖定是否有任何優勢?

/*Then the following should work.
  Remember: getHelper() is usually called many times, it is BAD 
  to call synchronized() every time for such a trivial thing!
*/
class Foo {

private Helper helper = null;
private Boolean isHelperInstantiated;
public Helper getHelper() {
    if (!isHelperInstantiated) {
        synchronized(this) {
            if (helper == null) {
                helper = new Helper();
                isHelperInstantiated = true;
            }
        }
    }
    return helper;
}

// other functions and members...
}    

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM