变量的volatile写入是否可以避免乱序写入?

Mac*_*Mac 0 java concurrency volatile non-volatile

volatile写入是否确保在一个线程中的任何写入(非易失性/易失性写入)在其他线程可见之前发生?

下面给出的代码总是会产生90,80输出吗?

public class MyClass
{
    private boolean flag = false;
    private volatile int volatileInt = 0;
    private int nonVolatileInt = 0;
    public void initVariables()
    {
        nonVolatileInt = 90; // non-volatile write
        volatileInt = 80; // volatile write
        flag = true; // non-volatile write
    }
    public void readVariables()
    {
        while (flag == false)
        {}
        System.out.println(nonVolatileInt + ","+ volatileInt);
    }
    public static void main(String st[])
    {
        final MyClass myClass = new MyClass();
        Thread writer = new Thread( new Runnable()
        {
            public void run()
            {
                myClass.initVariables();
            }
        });
        Thread reader = new Thread ( new Runnable()
        {
            public void run()
            {
                myClass.readVariables();
            }
        });
        reader.start();writer.start();
    }
}
Run Code Online (Sandbox Code Playgroud)

我关心的是initVariables()方法.是不是JVM可以自由地按以下方式重新排序代码块?:

flag = true;
nonVolatileInt = 90 ; 
volatileInt = 80;
Run Code Online (Sandbox Code Playgroud)

因此,我们将读取器线程的输出视为: 0,0
或者,它们可以按以下方式重新排序:

nonVolatieInt = 90;
flag = true;
volatileInt = 80;
Run Code Online (Sandbox Code Playgroud)

因此,我们将读者线程的输出视为: 90,0

Pet*_*rey 5

易失性写入确保在写入后不会出现已执行的写入.但是为了确保您看到这一点,您需要先执行易失性读取.

因此,我们将读者线程的输出视为: 90,0

正确.但是,如果您正确执行读取,则无法获得0, 80

0, 0 - ok
90, 0 - ok
90, 80 - ok
0, 80 - breaks happens before.
Run Code Online (Sandbox Code Playgroud)

但是,您的读取不能确保在行为之前发生,因为它不会先执行易失性读取.

System.out.println(nonVolatileInt + ","+ volatileInt);
Run Code Online (Sandbox Code Playgroud)

这首先读取非易失性字段,因此您可以看到旧版本的非易失性字段和新版本的volatile字段.

注意:实际上,您极不可能发现问题.这是因为缓存一次使整个缓存行无效,如果这些字段在同一个64字节块中,则不应该看到不一致.

更有可能是一个问题就是这个循环.

 while (flag == false)
    {}
Run Code Online (Sandbox Code Playgroud)

问题是; JIT可以看到你的线程nevers写入,flag因此它可以内联值.即它永远不需要读取价值.这可能导致无限循环.

http://vanillajava.blogspot.co.uk/2012/01/demonstrating-when-volatile-is-required.html

  • @Mac:请注意,如果你能确保在另一个线程写入之后读取`volatile`变量*,那么整个事情才有效.例如,如果`flag`是`volatile`,你的阅读线程必须等到它从`flag`读出`true`.只有这样,它才能继续读取其他变量,知道它将正确读取在`flag`设置为'true`之前写入的所有值. (2认同)