【问题标题】:Is reordering of instance initialization and assignment to a shared variable possible?是否可以重新排序实例初始化和分配给共享变量?
【发布时间】:2013-04-15 08:50:10
【问题描述】:

我正在阅读an essay,它实际上谈到了双重检查锁定,但我对作为示例的代码中更基本的故障感到惊讶。那里指出,实例的初始化(即在构造函数返回之前写入实例变量)可能会被重新排序到实例的引用被写入共享变量(以下示例中的静态字段)。

对于Foo 类的以下定义,如果一个线程执行Foo.initFoo(); 和另一个线程执行System.out.println(Foo.foo.a);,那么第二个线程可能会打印0(而不是1 或抛出一个NullPointerException)?

class Foo {
    public int a = 1;

    public static Foo foo;

    public static void initFoo() {
        foo = new Foo();
    }

    public static void thread1() {
        initFoo(); // Executed on one thread.
    }

    public static void thread2() {
        System.out.println(foo.a); // Executed on a different thread
    }
}

根据我对 Java 内存模型(以及其他语言中的内存模型)的了解,这实际上并不让我感到惊讶,但直觉强烈认为这是不可能的(可能是因为涉及对象初始化和对象初始化在 Java 中显得如此神圣)。

如果没有在第一个线程中同步,是否可以“修复”这段代码(即它永远不会打印0)?

【问题讨论】:

  • 您的代码似乎缺少 System.out.println() 就像您描述的那样。能贴出完整的代码吗?
  • 它也缺少对Foo.initFoo() 的调用。我已将语句添加为单独的静态函数,该函数将在不同线程上调用,而不会在线程之间进行同步。

标签: java concurrency


【解决方案1】:

foo = new Foo(); 的调用涉及多个操作,除非您引入适当的同步来防止它发生,否则这些操作可能会被重新排序:

  1. 为新对象分配内存
  2. 写入字段的默认值 (a = 0)
  3. 写入字段的初始值 (a = 1)
  4. 发布对新创建对象的引用

如果没有正确同步,第 3 步和第 4 步可能会重新排序(请注意,第 2 步必然发生在第 4 步之前),尽管 x86 架构上的热点不太可能发生这种情况。

为了防止它,您有几种解决方案,例如:

  • 使a最终
  • 同步访问foo(使用同步的init AND getter)。

无需深入了解 JLS #17 的复杂性,您可以阅读 JLS #12.4.1 关于类初始化(重点是我的):

初始化代码不受限制的事实允许构造示例,其中在评估其初始化表达式之前,当类变量仍具有初始默认值时,可以观察到它的值,但是这样实例在实践中很少见。 (也可以为实例变量初始化构造此类示例。)Java 编程语言的全部功能在这些初始化程序中可用;程序员必须小心谨慎。这种能力给代码生成器带来了额外的负担,但无论如何都会出现这种负担,因为 Java 编程语言是并发的。

【讨论】:

  • 你的回答让我松了一口气!我已经习惯了这样一个事实,即通过设置共享变量来发布 不可变 对象是没有同步的线程安全的,我从未想过它只是线程安全的事实因为对象的不变性!
  • @Feuermurmel 您应该知道两件事 1. 声明字段volatile 足以进行必要的同步,以防止重新排序提到的 assylias。 2. 一个完全不可变的对象(假设所有字段都是最终的)不会冒部分初始化对象的风险,我们在这里看到这是一个问题。
  • @JohnVint 这是否意味着声明静态字段 Foo.foo transient 而不是字段 Foo.a final 也可以解决问题(假设没有其他地方可以写入 Foo.a)?
  • @Feuermurmel 不是瞬态的,易变的。是的,使用public static volatile Foo foo;,您还拥有一个安全的出版物,a 保证被视为 1。
  • @GaborSch 哦,哇,我失败了!我的意思当然是volatile
【解决方案2】:

即使在 x86 下,也可以通过 JIT 编译器对实例初始化重新排序。 但是,编写可以触发这种重新排序的代码有点棘手。 关于如何重现这种重新排序,请参阅我的问题:

Is there any instruction reordering done by the Hotspot JIT compiler that can be reproduced?

【讨论】:

    猜你喜欢
    • 2022-12-09
    • 1970-01-01
    • 2021-06-18
    • 2014-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多