在哪些架构/操作系统中,其他线程可以在构造函数调用后看到默认的非最终字段值?

发布于 2024-11-14 20:25:01 字数 2449 浏览 3 评论 0原文

我试图在非最终字段的对象初始化不足的情况下重现内存可见性问题(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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

逆流 2024-11-21 20:25:01

阿尔法。 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.

紧拥背影 2024-11-21 20:25:01

为了获得您想要的结果,您可能会尝试打开深度优化,因此以 -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.

夜声 2024-11-21 20:25:01

我建议您获取一份“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'

深府石板幽径 2024-11-21 20:25:01

这也许应该是一个单独的问题......但它非常切题。这是我之前发表的评论的更广泛的版本。

jls 第 17.4 节的第一部分说:

要确定执行中线程 t 的操作是否合法,我们只需
评估线程 t 的实现,因为它将在单个线程中执行
线程上下文,如本规范其余部分中所定义。

我困惑的地方是理解“如本规范其余部分所定义的”对于程序顺序的含义。

在当前的情况下,分配

f = new FinalFieldExample();

受分配语义(第 15.26.1 节)的约束,其中涉及以下内容。规范中的格式错误(尤其是第三步)令人困惑,我相信我已经重新格式化它以准确反映意图。

[否则,]需要三个步骤

  1. 首先,计算左侧操作数以生成一个变量。如果此计算突然完成,则出于同样的原因,赋值表达式也会突然完成;右侧操作数不会被求值,也不会发生赋值。
  2. 否则,右侧操作数将会被求值。如果此计算突然完成,则赋值表达式会出于同样的原因突然完成并且不会发生赋值。
  3. 否则,右侧操作数的值将转换为左侧变量的类型,进行值集转换(第 5.1.13 节)到适当的标准值集(不是扩展指数值集),并且转换结果存储到变量中。

这读起来就像是规范单线程“程序顺序” 大部头书。我误解了什么?

答案也许是,真正想要的是“鸭子测试” - 如果单个线程执行就好像一切都按照指定的顺序完成,那么它就是正确的实现。但这部分的写法与其他地方使用“出现”一词来明确这一点的方式非常不同,例如:

Java 编程语言还保证运算符的每个操作数(条件运算符 &&、|| 和 ? : 除外)似乎在操作的任何部分之前都经过完全评估本身已执行。

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:

To determine if the actions of thread t in an execution are legal, we simply
evaluate the implementation of thread t as it would be performed in a single
threaded context, as defined in the rest of this specification.

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

f = new FinalFieldExample();

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:

  1. First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the right-hand operand is not evaluated and no assignment occurs.
  2. Otherwise, the right-hand operand is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  3. Otherwise, the value of the right-hand operand is converted to the type of the left-hand variable, is subjected to value set conversion (§5.1.13) to the appropriate standard value set (not an extended-exponent value set), and the result of the conversion is stored into the variable.

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.:

The Java programming language also guarantees that every operand of an operator (except the conditional operators &&, ||, and ? :) appears to be fully evaluated before any part of the operation itself is performed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文