Java 构造函数与指令重新排序
我只是偶然发现了一个我以前从未听说过,在其他地方也找不到的说法。这种说法是,从另一个线程的角度来看,构造函数返回的值的赋值可以根据构造函数内部的指令重新排序。换句话说,声明是在下面的代码中,另一个线程可以读取未设置Java 构造函数与指令重新排序,java,thread-safety,Java,Thread Safety,我只是偶然发现了一个我以前从未听说过,在其他地方也找不到的说法。这种说法是,从另一个线程的角度来看,构造函数返回的值的赋值可以根据构造函数内部的指令重新排序。换句话说,声明是在下面的代码中,另一个线程可以读取未设置x值的非空值a class MyInt { private int x; public MyInt(int value) { x = value; } public int getValue() { return x; } }
x
值的非空值a
class MyInt {
private int x;
public MyInt(int value) {
x = value;
}
public int getValue() {
return x;
}
}
MyInt a = new MyInt(42);
这是真的吗
编辑:
我认为可以保证,从执行MyInt a=new MyInt(42)
的线程的角度来看,x
的赋值与a
的赋值具有先发生后发生的关系。但这两个值都可以缓存在寄存器中,并且它们可能不会按照最初写入的顺序刷新到主内存中。因此,如果没有内存屏障,另一个线程可以在写入x
的值之前读取a
的值。对吗
那么,根据您的回答和下面的评论,这些对线程安全性的评估是正确的吗
// thread-safe
class Foo() {
final int[] x;
public Foo() {
int[] tmp = new int[1];
tmp[0] = 42;
x = tmp; // memory barrier here
}
}
// not thread-safe
class Bar() {
final int[] x = new int[1]; // memory barrier here
public Bar() {
x[0] = 42; // assignment may not be seen by other threads
}
}
如果这是正确的。。。哇,这真的很微妙。在Java内存模型的意义上-是的。然而,这并不意味着你会在实践中观察到它 从以下角度来看:可能导致可见重新排序的优化不仅可能发生在编译器中,也可能发生在CPU中。但是CPU对对象及其构造函数一无所知,对于处理器来说,这只是一对赋值,如果CPU的内存模型允许,可以对其重新排序 当然,编译器和JVM可能会指示CPU不要通过放入生成的代码来重新排序这些分配,但对所有对象这样做会破坏CPU的性能,CPU可能严重依赖于这种激进的优化。这就是为什么Java内存模型不为这种情况提供任何特殊保证的原因 例如,这导致了Java内存模型下的一个众所周知的缺陷 换句话说,声明是在下面的代码中,另一个线程可以读取a的非空值,其中x的值尚未设置
class MyInt {
private int x;
public MyInt(int value) {
x = value;
}
public int getValue() {
return x;
}
}
MyInt a = new MyInt(42);
简而言之,答案是肯定的
详细回答:另一个线程读取非空a
且值x
尚未设置的非空的关键点不是严格的指令重新排序,而是处理器缓存其寄存器中的值(和L1缓存),而不是从主存中读取这些值。这可能间接意味着重新排序,但这不是必需的
虽然CPU寄存器中的值缓存有助于加快处理速度,但它引入了在不同CPU上运行的不同线程之间的值可见性问题。如果总是从主程序区域读取值,则所有线程都会一致地看到相同的值(因为该值有一个副本)。在您的示例代码中,如果成员字段x
的值被缓存到由线程1访问的CPU1寄存器中,并且CPU-2上运行的另一个线程线程2现在从主内存读取并更新该值,那么从程序的角度来看,缓存在CPU-1(由线程1处理)中的该值现在无效,但是Java规范本身允许虚拟机将此视为有效的场景。您引用的文章在概念上是正确的。它的术语和用法有点不精确,正如你的问题一样,这会导致潜在的沟通错误和误解。我似乎在这里反复强调术语,但是Java内存模型非常微妙,如果术语不精确,那么理解就会受到影响
我将从你的问题(和评论)中摘录要点,并提供答案
构造函数返回的值的赋值可以根据构造函数内部的指令重新排序
几乎是的。。。可以重新排序的不是指令,而是内存操作(读和写)。一个线程可以以特定的顺序执行两条写指令,但是数据到达内存,以及这些写入到其他线程的可见性,可能以不同的顺序发生
我认为可以保证,从执行MyInt a=new MyInt(42)
的线程的角度来看,x
的赋值与a
的赋值具有先发生后发生的关系
同样,几乎是。确实,按照程序顺序,对x
的赋值发生在对a
的赋值之前。然而,before是一个适用于所有线程的全局属性,所以谈论before对于特定线程是没有意义的
但这两个值都可以缓存在寄存器中,并且它们可能不会按照最初写入的顺序刷新到主内存中。因此,如果没有内存屏障,另一个线程可以在写入x之前读取a的值
再一次,几乎是。值可以缓存在寄存器中,但部分内存硬件(如缓存或写缓冲区)也可能导致重新排序。硬件可以使用各种机制来更改顺序,例如缓存刷新或内存屏障(通常不会导致刷新,但只会防止某些重新排序)。然而,从硬件角度考虑这一点的困难在于,实际系统相当复杂,并且具有不同的行为。例如,大多数CPU都有几种不同的记忆障碍。如果您想对JMM进行推理,您应该考虑模型的元素:内存操作和同步,它们通过建立before关系来约束重新排序
因此,为了从JMM的角度重新审视这个示例,我们看到了对字段x
的写入和