不使用volatile关键字平台的影响是否具体?

时间:2013-06-27 16:59:26

标签: java concurrency volatile

不使用volatile关键字平台的影响是否具体?

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

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

我知道什么是挥发性保证以及什么时候应该使用。这不是问题。

示例:

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();
}}

6 个答案:

答案 0 :(得分:3)

Volatile在其效果方面不是特定于平台的。它符合Java Memory Model.中规定的规范。对于每个平台,JVM中的实现明显不同,关于它如何强制执行内存障碍。

您能提供示例代码吗?你的测试可能会产生误导。

答案 1 :(得分:1)

  

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

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

您无法通过运行少量测试用例来确定它无法正常工作。这些问题可能会在成千上万的测试中被捕获。

答案 2 :(得分:1)

  

不使用volatile关键字平台的影响是否具体?

Java内存模型描述了程序在正确同步时必须的行为方式,但没有详细说明程序 的行为 从例如因果关系的要求)。所以从理论上讲,它是特定于平台的。

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

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

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

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

另见this post on memory models

答案 3 :(得分: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将缓存线程test的{​​{1}}值。使线程b休眠将使b字段无法读取太多,然后JVM将不会尝试“优化”(在线程test上缓存test的值) - 但如果b被声明为volatile,则JVM无法缓存test的值。

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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

  • 对于性能优化,编译器& JVM实现可以移动代码顺序,也可以使用仅对一个线程可见的寄存器或缓存。显然,这种行为因每种实现而异。
  • 这意味着变量的更新值可能对一个线程可见;而所有其他线程都读取过时的变量
  • 如果您想安全地从多个线程访问变量,保证一致读取最新值,则must使用volatile。 volatile提供的此行为不是特定于平台的。
  • 您可能会在某些情况/实施情况下侥幸and and并且在没有volatile的情况下看到一致的最新值,但这是运气和运气。无法编程。