在哪些架构/操作系统中,其他线程可以在构造函数调用后看到默认的非最终字段值?
我试图在非最终字段的对象初始化不足的情况下重现内存可见性问题(JLS 17.5 最终字段语义, FinalFieldExample 类示例)。其中指出“但是,fy 不是最终的;因此不能保证 reader() 方法看到它的值 4”
我尝试过以下代码:
public class ReorderingTest2 {
public static void main(String[] args) {
for (int i = 0; i < 2500; i++) {
new Thread(new Reader(i)).start();
new Thread(new Writer(i)).start();
}
}
static class Reader implements Runnable {
private String name;
Reader(int i) {
this.name = "reader" + i;
}
@Override
public void run() {
//System.out.println(name + " started");
while (true) {
FinalFieldExample.reader(name);
}
}
}
static class Writer implements Runnable {
private String name;
Writer(int i) {
this.name = "writer" + i;
}
@Override
public void run() {
//System.out.println(name + " started");
while (true) {
FinalFieldExample.writer();
}
}
}
static class FinalFieldExample {
int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader(String name) {
if (f != null) {
int i = f.x;
int j = f.y;
if (i != 3 || j != 4) {
System.out.printf("reader %s sees it!%n", name);
}
}
}
}
}
如 之前我的类似主题 - 我在不同的 PC(从 2 核到 8 核)上尝试过 Windows,甚至在我们的服务器端 Solaris 上进行过尝试32 核心盒 - 我无法重现它:fx 和 fy - 总是已经正确初始化。
对于 Intel/x86/x64 架构我得到了答案 - 他们几乎有默认内存保证这样的构造函数逻辑重新排序。 Solaris/sparc 似乎也是如此?
那么在什么架构/操作系统中可以重现这种重新排序?
I'm trying to reproduce a memory visibility issue in case of insufficient object initialization for non-final fields (JLS 17.5 Final Field Semantics, FinalFieldExample class example). Where it stated "However, f.y is not final; the reader() method is therefore not guaranteed to see the value 4 for it"
I've tried this code:
public class ReorderingTest2 {
public static void main(String[] args) {
for (int i = 0; i < 2500; i++) {
new Thread(new Reader(i)).start();
new Thread(new Writer(i)).start();
}
}
static class Reader implements Runnable {
private String name;
Reader(int i) {
this.name = "reader" + i;
}
@Override
public void run() {
//System.out.println(name + " started");
while (true) {
FinalFieldExample.reader(name);
}
}
}
static class Writer implements Runnable {
private String name;
Writer(int i) {
this.name = "writer" + i;
}
@Override
public void run() {
//System.out.println(name + " started");
while (true) {
FinalFieldExample.writer();
}
}
}
static class FinalFieldExample {
int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader(String name) {
if (f != null) {
int i = f.x;
int j = f.y;
if (i != 3 || j != 4) {
System.out.printf("reader %s sees it!%n", name);
}
}
}
}
}
As in previous my similar topic - I've tried on different PCs (from 2 to 8 cores) with Windows and even on our server-side Solaris 32 core box - I couldn't reproduce it: f.x and f.y - are always already proper-initialized.
For Intel/x86/x64 architecture as I got the answer - they have pretty much default memery guarantees which prevent such constructor logic reordering.
Seems the same is true for Solaris/sparc too?
So in what architecture/OSes this reordering can be reproduced?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
阿尔法。 Paul E. McKenney 的书 并行编程很难吗?那么,你能做什么?有一章解释了最重要平台的内存模型。
Alpha. Paul E. McKenney's book Is Parallel Programming Hard, And, If So, What Can You Do About It? has a chapter explaining thememory model of the most important platforms.
为了获得您想要的结果,您可能会尝试打开深度优化,因此以
-server
模式运行程序。我首先想到的是让
f
易失性
,但这显然会搞砸整个实验。打开即时编译器的 XML 日志记录(如果您使用的是 HotSpot JVM),并查看生成的机器代码(使用一些外部调试器或内存转储程序)。然后,您可以检查生成的代码是否可以允许您观察到您想要的结果。
To get the result you want, you might try to turn on heavy optimization, so run the program in
-server
mode.I first thought of making
f
volatile
, but that would obviously screw the whole experiment.Turn on the XML logging for the just-in-time compiler (if you are using the HotSpot JVM), and look at the generated machine code (using some external debugger or memory dumper). Then you can examine the generated code if that would even allow you to observe the result you want.
我建议您获取一份“Java 并发实践”并阅读第 3 章,其中详细介绍了 JVM 关于锁定和可见性的保证。你的问题与特定的架构无关,而与理解 Java 中的happens-before有关。
我认为您无法重现该问题,因为 FinalFieldExample 构造函数末尾有一个发生前边缘,它保证 x = 3 和 y = 4;
顺便提一句。 FinalFieldExample 对象有点混乱。它想成为一个适当的单例,但你没有那样编码。静态“f”周围缺乏同步使得推断此类的运行时行为变得更加困难。我认为它应该是一个适当的单例,具有同步保护对静态“f”的访问,并且您应该调用编写器和读取器方法,例如...
FinalFieldExample.getInstance().writer();
只是说说而已
I suggest you acquire a copy of "Java Concurrency in Practice" and read Chapter 3 which details the JVM guarantees around locking and visibility. Your question has nothing to do with a specific architecture and everything to do with understanding happens-before in Java.
I think that you cannot repro the problem because there is a happens-before edge at the end of the FinalFieldExample constructor which is guaranteeing that x = 3 and y = 4;
BTW. The FinalFieldExample object is a bit of a mess. It wants to be a proper singleton, but you didn't code it that way. The lack of synchronization around the static "f" makes it more difficult than it should be to reason about the runtime behavior of this class. Methinks it should be a proper singleton with synchronization protecting access to the static "f" and you should be calling the writer and reader methods like...
FinalFieldExample.getInstance().writer();
Just sayin'
这也许应该是一个单独的问题......但它非常切题。这是我之前发表的评论的更广泛的版本。
jls 第 17.4 节的第一部分说:
我困惑的地方是理解“如本规范其余部分所定义的”对于程序顺序的含义。
在当前的情况下,分配
受分配语义(第 15.26.1 节)的约束,其中涉及以下内容。规范中的格式错误(尤其是第三步)令人困惑,我相信我已经重新格式化它以准确反映意图。
[否则,]需要三个步骤:
这读起来就像是规范单线程“程序顺序” 大部头书。我误解了什么?
答案也许是,真正想要的是“鸭子测试” - 如果单个线程执行就好像一切都按照指定的顺序完成,那么它就是正确的实现。但这部分的写法与其他地方使用“出现”一词来明确这一点的方式非常不同,例如:
This should perhaps be a separate question... but it's very on-point. It's a more expansive version of a comment I made earlier.
The first part of section 17.4 of the jls says:
The place I get hung up is understanding what "as defined in the rest of this specification" means, with regard to program order.
In the case at hand, the assignment
is subject to assignment semantics (section 15.26.1), of which the following pertains. It is confusingly misformatted in the spec (the third step especially), I believe I have reformatted it to accurately reflect intent.
[Otherwise,] three steps are required:
This reads like a specification of single-thread "program order" to me. What am I misinterpreting?
An answer, perhaps, is that what is really intended is a "duck test" - if a single thread executes as if everything were done in the order specified, it's a correct implementation. But this section is written very differently from other places where this is made clear by using the word appear, e.g.: