0

不使用 volatile 关键字平台的影响是特定的吗?

在带有 openJDK 1.7 的 Ubuntu 13.04 x64 上,使用或不使用 volatile 关键字均无效;从某种意义上说,程序在不使用 volatile 时按预期执行。

我想知道这个的确切原因是什么以及为什么它不会像在 Windows 中使用 Oracle 的 JVM 那样每次都失败。

我知道什么是 volatile 保证以及何时应该使用它。这不是问题。

例子:

public class VolatileTest {
private static boolean test;
public static void main(String... args) {
    Thread a = new Thread(new Runnable() {

        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            test = true;

        }
    });

    Thread b = new Thread(new Runnable() {
        @Override
        public void run() {
            while(!test) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(test);
            }
        }
    });

    a.start();
    b.start();
}}
4

6 回答 6

3

Volatile 的效果不是特定于平台的。它符合Java 内存模型中规定的规范。它在 JVM 中的实现对于每个平台来说显然是不同的,至于它如何强制执行内存屏障。

你能提供示例代码吗?您的测试可能具有误导性。

于 2013-06-27T17:01:42.260 回答
1

不使用 volatile 关键字平台的影响是特定的吗?

Java 内存模型描述了程序在正确同步时必须如何表现,但没有说明程序在不同步时如何表现(例如因果关系要求除外)。所以理论上,它是特定于平台的。

在实践中,它是特定于平台和 JVM 的。通常,x86 体系结构具有相当强大的内存模型,并且删除volatile关键字通常不会破坏您的程序(即代码仍然表现得好像变量仍然是volatile)。

一些(通常较旧的)处理器甚至是顺序一致的,这意味着您的程序将表现得好像一切都是同步的。

另一方面,在 ARM 等内存模型较弱的处理器上,更容易观察到损坏的多线程程序的影响。

类似地,一些 JVM 在优化方面更加激进,并且会以不同的方式重新排序指令、提升变量等。对于给定的 JVM,您使用的参数也可能会产生影响。例如在 Hotspot 上,如果您禁用 JIT 编译,您可能会“修复”一些线程安全问题。

另请参阅有关内存模型的这篇文章

于 2013-07-11T09:39:11.117 回答
1

一个字段可能被声明为 volatile,在这种情况下,Java 内存模型确保所有线程都能看到变量的一致值。-- JLS 8.3.1.4

Volatile 保证变量的一致视图。volatile 的本机实现禁止 CPU/Core 将变量保留在其寄存器中以供线程执行的计算,以便在其他 CPU/Core 上运行的所有线程都可以对该变量具有一致的视图。

您不能通过运行几个测试用例来断定它不起作用。这些问题可能会在成千上万的测试中被捕获。

于 2013-06-27T17:16:31.273 回答
0

您提供的示例代码没有充分利用循环来优化 JIT。它将在解释器中运行,因此您的代码(很可能)不会受到任何会导致其失败的编译器优化技巧的影响。CPU 本身也不太可能使用足够先进的技巧来阻止此代码,特别是考虑到这些睡眠涉及的相对巨大的时间尺度。

但是,这是特定 JVM 的实现细节。即使错误永远不会出现在您的硬件、JVM 和操作系统的特定组合上,您的程序也很活泼。

于 2013-06-27T21:00:17.633 回答
0
  • 对于性能优化,编译器和 JVM 实现可能会移动代码顺序并使用仅对一个线程可见的寄存器或缓存。显然,这种行为因每个实现而异。
  • 这意味着一个变量的更新值可能对一个线程可见;而所有其他线程读取一个陈旧的 varlue
  • 如果你想从多个线程安全地访问一个变量,保证一致地读取最新的值,你可以must使用volatile. volatile 提供的这种行为不是特定于平台的。
  • 您可能会在某些场景/实现下对它进行侥幸,并在没有 的情况下看到一致的最新值volatile,但这是运气,无法编程。
于 2013-06-28T02:53:24.317 回答
0

声明一个字段 volatile 确保所有线程都能看到该字段的当前值。不声明字段 volatile 并不能确保其他线程不会看到线程对字段所做的修改。

稍微修改一下你的例子:

public class VolatileTest {

private static boolean test = false;

public static void main(String... args) {
    Thread a = new Thread(new Runnable() {

        @Override
        public void run() {

            boolean localTest = test;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println("changing local test");
            test = !localTest;

        }
    });

    Thread b = new Thread(new Runnable() {
        @Override
        public void run() {
            boolean localTest = test;
            while(!localTest) {
                localTest = test;
            }
        }
    });

    a.start();
    b.start();
}}

这会读取该字段test很多次,然后 JVM 将为testthread 缓存值b。使线程b休眠将使test字段不会被读取太多,然后 JVM 不会尝试“优化”(缓存teston thread的值b) - 但如果test被声明为 volatile,JVM 将无法缓存test的值。

该测试在 Ubuntu Lucid 64 位上运行,使用 Oracle 的 Java 1.7.0。

于 2013-06-27T17:42:00.933 回答