简体   繁体   English

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

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

This question relates to behaviour of old Java versions and old implementations of the double checked locking algorithm 此问题涉及旧Java版本的行为和双重检查锁定算法的旧实现

Newer implementations use volatile and rely on slightly changed volatile semantics, so they are not broken. 较新的实现使用volatile并依赖于稍微改变的volatile语义,因此它们不会被破坏。


It's stated that fields assignment is always atomic except for fields of long or double. 据说,除了long或double字段外,字段赋值总是原子的。

But, when I read an explaination of why double-check locking is broken, it's said that the problem is in assignment operation: 但是,当我读到为什么双重检查锁定被破坏的解释时,它说问题在于赋值操作:

// 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. Thread A notices that the value is not initialized, so it obtains the lock and begins to initialize the value. 线程A注意到该值未初始化,因此它获得锁定并开始初始化该值。
  2. Due to the semantics of some programming languages, the code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization. 由于某些编程语言的语义,允许编译器生成的代码在A完成初始化之前更新共享变量以指向部分构造的对象。
  3. Thread B notices that the shared variable has been initialized (or so it appears), and returns its value. 线程B注意到共享变量已初始化(或显示),并返回其值。 Because thread B believes the value is already initialized, it does not acquire the lock. 因为线程B认为该值已经初始化,所以它不会获得锁定。 If B uses the object before all of the initialization done by A is seen by B (either because A has not finished initializing it or because some of the initialized values in the object have not yet percolated to the memory B uses (cache coherence)), the program will likely crash. 如果B在B看到A完成的所有初始化之前使用该对象(因为A尚未完成初始化或者因为对象中的某些初始化值尚未渗透到内存B使用(缓存一致性)) ,该程序可能会崩溃。
    (from http://en.wikipedia.org/wiki/Double-checked_locking ). (来自http://en.wikipedia.org/wiki/Double-checked_locking )。

When is it possible? 什么时候可能? Is it possible that on 64-bit JVM assignment operation isn't atomic? 是否有可能在64位JVM分配操作中不是原子的? If no then whether "double-checked locking" is really broken? 如果不是那么“双重检查锁定”是否真的被打破了?

The problem is not atomicity, it's ordering. 问题不在于原子性,而在于它的排序。 The JVM is allowed to reorder instructions in order to improve performance, as long as happens-before is not violated. 只要不违反发生之前的情况 ,JVM就可以重新排序指令以提高性能。 Therefore, the runtime could theoretically schedule the instruction that updates helper before all instructions from the constructor of class Helper have executed. 因此,运行时理论上可以在执行类Helper的构造函数的所有指令之前调度更新helper程序的指令。

The assignment of the reference is atomic, but the construction is not! 引用的赋值是原子的,但构造不是! So as stated in the explanation, supposing thread B wants to use the singleton before Thread A has fully constructed it, it cannot create a new instance because the reference is not null, so it just returns the partially constructed object. 因此,如解释中所述,假设线程B想要在线程A完全构造它之前使用单例,它不能创建新实例,因为引用不是null,因此它只返回部分构造的对象。

If you do not ensure that publishing the shared reference happens before another thread loads that shared reference, then the write of the reference to the new object can be reordered with the writes to its fields. 如果您不确保在另一个线程加载该共享引用之前发布共享引用,则可以通过写入其字段来重新排序对新对象的引用的写入。 In that case, another thread could see an up-to-date value for the object reference but out of date values for some or all of the object's state - a partially constructed object. 在这种情况下,另一个线程可以看到对象引用的最新值,但是对象的某些或全部状态的过期值 - 部分构造的对象。 -- Brian Goetz: Java Concurrency in Practice - Brian Goetz:Java Concurrency in Practice

Since the initial check for null is not synchronized there is no publication and this reordering is possible. 由于初始检查null未同步,因此没有发布,并且可以进行此重新排序。

Several assignments may be needed to construct the instance of Helper inside the constructor, and the semantics allows that they are reordered with respect to the assignment helper = new Helper() . 可能需要在构造函数中构造Helper实例的几个赋值,并且语义允许它们相对于赋值helper = new Helper()重新排序。

So the field helper may be assigned a reference to an object where not all assignments have taken place, so that it is incompletely initialized. 因此,可以为字段helper分配对未进行所有分配的对象的引用,以使其未完全初始化。

Double checked locking in java has a variety of problems: 在java中双重检查锁定有各种各样的问题:

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

阅读这篇文章: http//www.javaworld.com/jw-02-2001/jw-0209-double.html即使你不理解所有细节(像我一样),只要相信这个好方法不起作用。

I'm sorry this might be a bit irrelevant to the question, I'm just curious. 对不起,这可能与这个问题有点无关,我只是好奇。 In this case wouldn't it better to acquire the lock before the assignment and/or returning the value? 在这种情况下,在分配之前获取锁和/或返回值是不是更好? Like: 喜欢:

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

Or is there any advantage of using the double-checked locking? 或者使用双重检查锁定是否有任何优势?

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