Java内存模型中的部分构造对象

Java内存模型中的部分构造对象,java,concurrency,final,java-memory-model,Java,Concurrency,Final,Java Memory Model,我遇到了以下代码: 文章说, 编译器(JIT、CPU等)不会对构造函数进行特殊处理,因此允许对构造函数中的指令和构造函数后面的指令进行重新排序 另外,关于Java内存模型,声明 只有在对象完全初始化后才能看到该对象引用的线程才能确保看到该对象最终字段的正确初始化值 上面提到的MyInt实例似乎是不可变的(除了类没有标记为final)和线程安全的,但文章中指出它不是。它们指出,不能保证x在读取时始终具有正确的值 但我认为 只有创建对象的线程才能在构造对象时访问它 而这似乎是如此的支持 我的问题是:

我遇到了以下代码:

文章说,

编译器(JIT、CPU等)不会对构造函数进行特殊处理,因此允许对构造函数中的指令和构造函数后面的指令进行重新排序

另外,关于Java内存模型,声明

只有在对象完全初始化后才能看到该对象引用的线程才能确保看到该对象最终字段的正确初始化值

上面提到的
MyInt
实例似乎是不可变的(除了类没有标记为
final
)和线程安全的,但文章中指出它不是。它们指出,不能保证
x
在读取时始终具有正确的值

但我认为

只有创建对象的线程才能在构造对象时访问它

而这似乎是如此的支持


我的问题是:这是否意味着,对于当前的JMM,由于指令重新排序,线程可以访问部分构造的对象?如果是,怎么做?这是否意味着Java教程中的语句根本不正确?

那篇文章是说,如果您有如下代码

foo = new MyInt(7);
在具有字段的类中

MyInt foo;
然后是相当于

(reference to new object).x = 7;
foo = (reference to new object);
可以作为某种优化进行交换。这永远不会改变运行此代码的线程的行为,但有可能其他线程能够读取行后的
foo

foo = (reference to new object);
但在排队之前

(reference to new object).x = 7;
在这种情况下,它会将
foo.x
视为
0
,而不是
7
。也就是说,其他线程可以运行

int bar = someObject.getFoo().getValue();
最后的
bar
等于
0


我从未见过这样的事情在野外发生,但作者似乎知道他在说什么。

单是指令重新排序不能导致另一个线程看到部分构造的对象。根据定义,JVM只允许在不影响正确同步的程序行为的情况下重新排序

对象引用的不安全发布会导致糟糕的事情发生。举个例子,这里有一个关于单身汉的特别糟糕的尝试:

public class BadSingleton {
   public static BadSingleton theInstance;

   private int foo;

   public BadSingleton() {
      this.foo = 42;
      if (theInstance == null) {
         theInstance = this;
      }
   }
}
在这里,您意外地在
静态
字段中发布了对正在构造的对象的引用。在JVM决定对事物重新排序并将
This.foo=42
放置在
instance
之后之前,这不一定是个问题。因此,这两件事合在一起会破坏你的不变量,并允许另一个线程看到一个
BadSingleton。instance
及其
foo
字段未初始化

意外发布的另一个常见来源是从构造函数调用可重写方法。这并不总是导致意外发布,但存在潜在的可能性,因此应避免

只有创建对象的线程才能在构造对象时访问它

这是否意味着Java教程中的语句是 难道不是真的吗


是和否。这取决于我们如何解释单词
应该
。无法保证在任何可能的情况下,另一个线程都不会看到部分构造的对象。但是从某种意义上说,你应该编写不允许它发生的代码。

另一个线程如何引用一个构造函数尚未完成执行的对象?@daniu这也是我想知道的事情,因此我感到困惑。我很确定答案在于编译器规范/实现的细节。一般来说,虽然可以“允许对构造函数中的指令和构造函数后面的指令进行重新排序”,但我很确定该对象的赋值不算作交换的有效指令。“指令重新排序本身不能导致另一个线程看到部分构造的对象。”当然可以,如达伍德的回答所示。您给出的代码示例与此无关,它是一个不正确构造的示例。@Oleg错了,他的示例只有在对象引用被不安全地发布时才有效。如果没有不安全的发布,另一个线程就无法看到部分构造的对象。反之亦然,对于不安全发布,您不需要指令重新排序来查看部分构造的对象。因此,指令重新排序本身不能导致看到部分构造的对象。当然,但指令重新排序在非安全发布时可能单独导致问题。这就像说如果你不挡道,子弹就杀不了你。很明显,一个对象需要不安全地发布才能存在问题,这里描述的问题是由指令重新排序引起的。好吧,无论如何,我删除了我的下一票。我对同一个问题的最后一句话是肯定的,当对象被不安全地发布时,它会自己制造问题,我同意你们其他人的评论,我们相互理解,主要是在语义上争论。@Oleg谢谢,我重新阅读了我的答案,我想我现在更理解你们的担忧了。我已经改变了这个例子,来演示这两件事是如何相互作用并把事情搞得一团糟的。
public class BadSingleton {
   public static BadSingleton theInstance;

   private int foo;

   public BadSingleton() {
      this.foo = 42;
      if (theInstance == null) {
         theInstance = this;
      }
   }
}