我遇到了以下代码https://redis.io/topics/quickstart#starting-redis:
public class MyInt {
private int x;
public MyInt(int y) {
this.x = y;
}
public int getValue() {
return this.x;
}
}
文章指出
编译器(JIT,CPU等)不会对构造函数进行特殊处理,因此允许对构造函数中的指令和构造函数之后的指令进行重新排序。
此外,关于Java内存模型的in an article somewhere on the Internet表明
在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值。
上面提到的MyInt
实例似乎是不可变的(除了该类没有标记final
)和线程安全,但文章声明它不是。他们声明,x
无法保证在阅读时始终具有正确的值。
但我认为
只有创建对象的线程在构造时才能访问它
而this JSR-133 article似乎是如此支持。
我的问题是:是否意味着,使用当前的JMM,由于指令重新排序,线程可以访问部分构造的对象?如果是,怎么样?这是否意味着Java教程中的陈述根本不正确?
答案 0 :(得分:5)
那篇文章说如果你有像
这样的代码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
。
我从来没有见过这样的事情发生在野外,但作者似乎知道他在谈论什么。
答案 1 :(得分:1)
单独的指令重新排序不能导致另一个线程看到部分构造的对象。根据定义,如果JVM不影响正确同步的程序的行为,则只允许重新排序。
这是不安全的对象引用发布,可以使坏事发生。例如,对于单身人士来说,这是一次特别糟糕的尝试:
public class BadSingleton {
public static BadSingleton theInstance;
private int foo;
public BadSingleton() {
this.foo = 42;
if (theInstance == null) {
theInstance = this;
}
}
}
在这里,您不小心发布了对static
字段中正在构造的对象的引用。在JVM决定重新排序事物并将this.foo = 42
分配到theInstance
之后,这不一定是个问题。因此,这两件事共同打破你的不变量,并允许另一个线程看到BadSingleton.theInstance
字段未被初始化的foo
。
意外发布的另一个常见原因是从构造函数中调用可重写的方法。这并不总是导致意外发布,但潜力是存在的,因此应该避免。
只有创建对象的线程在构造时才能访问它
这是否意味着Java Tutorials的声明是 根本不是真的吗?
是和否。这取决于我们如何解释单词should
。无法保证在每种可能的情况下,另一个线程都不会看到部分构造的对象。但从某种意义上说,你应该编写不允许它发生的代码。