为什么在Java中双重检查锁定被破坏?

时间:2011-02-07 21:15:47

标签: java multithreading synchronization locking anti-patterns

此问题与旧Java版本的行为和双重检查锁定算法的旧实现有关

较新的实现use 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.   
  3. 由于某些编程语言的语义,代码   允许编译器生成   将共享变量更新为point   到部分构造的对象   在A完成表演之前   初始化。
  4.   
  5. 线程B注意到共享变量已初始化(或左右)   它出现),并返回其值。   因为线程B认为价值是   已初始化,但事实并非如此   获得锁。如果B使用该对象   在完成所有初始化之前   由A看到A(因为A   尚未完成初始化或   因为一些初始化的值   在对象尚未渗透   到内存B使用(缓存   连贯)),程序很可能   崩溃。   
    (来自http://en.wikipedia.org/wiki/Double-checked_locking)。
  6.   

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

7 个答案:

答案 0 :(得分:15)

问题不是原子性,而是排序。只要未违反happens-before,JVM就可以重新排序指令以提高性能。因此,运算符理论上可以在来自类helper的构造函数的所有指令执行之前调度更新Helper的指令。

答案 1 :(得分:7)

引用的赋值是原子的,但构造不是!因此,如解释中所述,假设线程B想要在线程A完全构造它之前使用单例,它不能创建新实例,因为引用不是null,因此它只返回部分构造的对象。

  

如果您不确保发布   共享引用发生在之前   另一个线程加载共享   参考,然后写的   引用新对象即可   对其写入重新排序   领域。在那种情况下,另一个线程   可以看到最新的价值   对象引用但过时了   部分或全部对象的值   国家 - 部分建构的   宾语。 - Brian Goetz:Java Concurrency in Practice

由于初始检查null未同步,因此没有发布,并且可以进行此重新排序。

答案 2 :(得分:2)

在构造函数中构造Helper的实例可能需要多个赋值,并且语义允许它们根据赋值helper = new Helper()进行重新排序。

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

答案 3 :(得分:2)

在java中进行双重检查锁定存在各种问题:

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

答案 4 :(得分:1)

阅读本文:http://www.javaworld.com/jw-02-2001/jw-0209-double.html 即使你不理解所有的细节(像我一样),只要相信这个好的技巧就行不通了。

答案 5 :(得分:0)

对不起,这可能与这个问题有点无关,我只是好奇。 在这种情况下,在分配之前获取锁和/或返回值是不是更好?像:

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();
    }
}

或者使用双重检查锁定是否有任何优势?

答案 6 :(得分:-1)

/*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...
}