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

发布于 2024-10-16 07:18:03 字数 1550 浏览 3 评论 0原文

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

新实现 使用 易失性 并依赖于稍微改变的易失性语义,因此它们破碎的。


据说,除了 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 使用该对象 在所有初始化完成之前 A 被 B 看到(或者因为 A 尚未完成初始化或 因为一些初始化值 在对象中尚未渗透 到内存 B 使用(缓存 连贯性)),该程序可能会 碰撞。
    (来自http://en.wikipedia.org/wiki/Double-checked_locking).

什么时候可以呢? 64 位 JVM 上的赋值操作是否可能不是原子的? 如果不是,那么“双重检查锁定”是否真的被破坏了?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

我不在是我 2024-10-23 07:18:03

问题不在于原子性,而在于顺序。允许 JVM 对指令重新排序以提高性能,只要 发生在之前没有被违反。因此,理论上,运行时可以在执行 Helper 类构造函数的所有指令之前安排更新 helper 的指令。

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.

暖风昔人 2024-10-23 07:18:03

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

如果您不确保发布
共享引用发生在之前
另一个线程加载共享的
参考,然后写入
对新对象的引用可以是
重新排序并写入其
字段。在这种情况下,另一个线程
可以看到最新的值
对象引用但已过时
部分或全部对象的值
state - 部分构建的状态
目的。 -- Brian Goetz:Java 并发实践

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

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.

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

Since the initial check for null is not synchronized there is no publication and this reordering is possible.

陌路终见情 2024-10-23 07:18:03

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

因此,helper 字段可能被分配了对某个对象的引用,但并未发生所有分配,因此它未完全初始化。

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().

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.

温柔女人霸气范 2024-10-23 07:18:03

java中的双重检查锁定有各种各样的问题:

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

Double checked locking in java has a variety of problems:

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

心如狂蝶 2024-10-23 07:18:03

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

Read this article: http://www.javaworld.com/jw-02-2001/jw-0209-double.html
Even if you did not understand all details (like me) just believe that this nice trick does not work.

相守太难 2024-10-23 07:18:03
/*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...
}    
/*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...
}    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文