14

如果非最终字段的对象初始化不足(JLS 17.5 Final Field SemanticsFinalFieldExample类示例),我正在尝试重现内存可见性问题。它声明“但是,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 似乎也是如此?

那么在哪些架构/操作系统中可以重现这种重新排序?

4

4 回答 4

1

Α。Paul E. McKenney 的书是并行编程很难,如果是,你能做些什么呢?有一章解释最重要平台的内存模型。

于 2011-06-11T08:32:33.760 回答
0

我建议您获取一份“Java Concurrency in Practice”并阅读第 3 章,其中详细介绍了 JVM 对锁定和可见性的保证。您的问题与特定架构无关,而与理解发生在 Java 中之前发生的一切有关。

我认为你不能重现这个问题,因为在 FinalFieldExample 构造函数的末尾有一个happens-before边缘,它保证 x = 3 和 y = 4;

顺便提一句。FinalFieldExample 对象有点乱。它想成为一个合适的单身人士,但你没有那样编码。围绕静态“f”缺乏同步使得推理此类的运行时行为比应该的更加困难。我认为它应该是一个适当的单例,具有同步保护对静态“f”的访问,你应该调用 writer 和 reader 方法,比如......

FinalFieldExample.getInstance().writer();

只是在说'

于 2011-06-23T19:37:28.093 回答
0

为了得到你想要的结果,你可能会尝试打开重度优化,所以在-server模式下运行程序。

我首先想到了制作f volatile,但这显然会搞砸整个实验。

打开即时编译器的 XML 日志记录(如果您使用的是 HotSpot JVM),并查看生成的机器代码(使用一些外部调试器或内存转储器)。然后,您可以检查生成的代码,如果这甚至可以让您观察到您想要的结果。

于 2011-06-11T06:51:41.497 回答
0

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

jls 第 17.4 节的第一部分说:

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

我被挂断的地方是理解“如本规范其余部分所定义”的含义,关于程序顺序。

在手头的情况下,任务

f = new FinalFieldExample();

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

[否则,]需要三个步骤

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

这对我来说就像是单线程“程序顺序”的规范。我在误解什么?

一个答案或许是,真正的意图是“鸭子测试”——如果一个线程执行就好像所有事情都按照指定的顺序完成,那么它就是一个正确的实现。但是这部分的写法与其他地方通过使用出现这个词来明确这一点非常不同,例如:

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

于 2011-07-09T15:00:12.320 回答