java - 对象构造在实践中是否保证所有线程都看到已初始化的非最终字段?

标签 java multithreading jvm java-memory-model

Java memory model保证对象的构造和终结器之间存在先行关系:

There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.

以及final字段的构造函数和初始化:

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

对于 volatile 字段也有保证,因为对于所有对此类字段的访问存在先行关系:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

但是常规的、好的旧的非 volatile 字段呢?我见过很多多线程代码,在使用非 volatile 字段构造对象后,它们不会费心创建任何类型的内存屏障。但我从未见过或听说过任何问题,而且我自己也无法重新创建这样的部分构造。

现代 JVM 是否只是在构建之后设置内存屏障?避免在施工期间重新排序?还是我只是幸运?如果是后者,是否可以随意编写重现部分构造的代码?

编辑:

为了澄清,我说的是以下情况。假设我们有一个类:

public class Foo{
    public int bar = 0;

    public Foo(){
        this.bar = 5;
    }
    ...
}

并且一些线程 T1 实例化了一个新的 Foo 实例:

Foo myFoo = new Foo();

然后将实例传递给其他线程,我们称之为T2:

Thread t = new Thread(() -> {
     if (myFoo.bar == 5){
         ....
     }
});
t.start();

T1 执行了我们感兴趣的两次写入:

  1. T1 将值 5 写入新实例化的 myFoobar
  2. T1 将对新创建对象的引用写入myFoo 变量

对于 T1,我们得到一个 guarantee写#1 发生在之前写#2:

Each action in a thread happens-before every action in that thread that comes later in the program's order.

但就 T2 而言,Java 内存模型不提供此类保证。没有什么能阻止它以相反的顺序看到写入。所以它可以看到一个完全构建的 Foo 对象,但是 bar 字段等于 0。

编辑2:

我在写完上面的例子几个月后又看了一遍。由于 T2 是在 T1 写入之后启动的,因此该代码实际上可以保证正常工作。这使它成为我想问的问题的错误示例。修复它以假定当 T1 执行写入时 T2 已经在运行。假设 T2 正在循环读取 myFoo,如下所示:

Foo myFoo = null;
Thread t2 = new Thread(() -> {
     for (;;) {
         if (myFoo != null && myFoo.bar == 5){
             ...
         }
         ...
     }
});
t2.start();
myFoo = new Foo(); //The creation of Foo happens after t2 is already running

最佳答案

以您的示例作为问题本身 - 答案是,这是完全可能的。正如您引用的那样,初始化字段对构造线程可见。这称为安全发布(但我打赌你已经知道了)。

事实是您没有通过实验看到这一点是 x86 上的 AFAIK(作为一个强大的内存模型),商店不会重新排序,所以除非 JIT 会重新排序这些商店T1 做到了 - 你看不到。但那是在玩火,字面意思,this question和后续行动(接近相同)here一个人(不确定是否属实)丢失了 1200 万台设备

JLS 只保证了几种实现可见性的方法。顺便说一句,这不是相反,JLS 不会说什么时候会中断,它会说什么时候会工作

1) final field semantics

请注意该示例如何显示每个 字段必须是final - 即使在当前实现下一个单个 就足够了,并且在构造函数之后插入了两个内存屏障(当使用 final 时):LoadStoreStoreStore

2) volatile fields (并且隐含地 AtomicXXX);我认为这个不需要任何解释,而且你似乎引用了这个。

3) Static initializers好吧,在我看来应该是显而易见的

4) Some locking involved - 这也应该是显而易见的,发生在规则之前...

关于java - 对象构造在实践中是否保证所有线程都看到已初始化的非最终字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51695962/

相关文章:

java - 为什么 Java 8 中的新 java.util.Arrays 方法没有为所有原始类型重载?

java - 消息 "operations coalesced during safepoint"的含义

java - Axon:如何为单个事件配置 amqp 发布?

multithreading - WP7-在停用或关闭应用程序时无法正常退出bg线程时遇到麻烦

Java 线程 - 等待数据返回,但不阻塞其他线程

java - 如何分析JVM垃圾收集?

java - 将值添加到 JSONArray 时如何保持值的顺序?

ios - 如何在后台创建多个对象?

java - Eclipse:JVM 共享库不包含 JNI_CreateJavaVM 符号

java - 查找JVM分配的内存