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

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

较新的实现使用volatile 并依赖于稍微改变的 volatile 语义,因此它们没有损坏.

<小时>

声明字段分配始终是原子的,除了 long 或 double 字段.

但是,当我读到为什么双重检查锁定被破坏的解释时,据说问题在于赋值操作:

//损坏的多线程版本//双重检查锁定"成语类Foo {私人助手 helper = null;公共助手 getHelper() {如果(助手 == null){同步(这个){如果(助手 == null){助手 = 新助手();}}}返回助手;}//其他函数和成员...}

<块引用>

  1. 线程 A 注意到该值没有被初始化,所以它获取了锁定并开始初始化价值.
  2. 由于某些编程语言的语义,代码允许编译器生成更新共享变量指向到一个部分构造的对象在 A 完成执行之前初始化.
  3. 线程 B 注意到共享变量已被初始化(或它出现),并返回其值.因为线程 B 认为值是已经初始化,它没有获取锁.如果 B 使用对象在所有初始化完成之前A 被 B 看到(或者因为 A尚未完成初始化或因为一些初始化值在物体中尚未渗出到内存 B 使用(缓存连贯性)),该程序可能碰撞.
    (来自 http://en.wikipedia.org/wiki/Double-checked_locking).

什么时候可以?64位JVM分配操作是否可能不是原子的?如果不是,那么双重检查锁定"是否真的被破坏了?

解决方案

问题不在于原子性,而在于顺序.JVM 可以重新排序指令以提高性能,只要 happens-before 没有被违反.因此,运行时理论上可以在类 Helper 的构造函数的所有指令执行之前调度更新 helper 的指令.

This question relates to behaviour of old Java versions and old implementations of the double checked locking algorithm

Newer implementations use volatile and rely on slightly changed volatile semantics, so they are not broken.


It's stated that fields assignment is always atomic except for fields of long or 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.
  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.
  3. Thread B notices that the shared variable has been initialized (or so it appears), and returns its value. Because thread B believes the value is already initialized, it does not acquire the lock. 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.
    (from 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? 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. Therefore, the runtime could theoretically schedule the instruction that updates helper before all instructions from the constructor of class Helper have executed.

相关文章