易失性变量和非易失性重新排序/可见性

Volatile variable and non volatile reordering / visibility

所以我认为我对这些东西已经足够了解了,直到我读到一些让我怀疑我对这个主题的知识的东西。我几乎可以肯定这本书是不正确的,但也想问问社区。

PS: 没看过书里的勘误表,估计是误报。

一个简化的例子:

public class VolatileMain {

private volatile int a = 0;
private String text = "";

public static void main(String[] args) throws Exception {

    VolatileMain vm = new VolatileMain();

    Thread writer = new Thread() {

        @Override
        public void run() {
            System.out.println("Running thread " + Thread.currentThread().getName());
            vm.text = "hello world";
            vm.a = 5;
        }
    };

    writer.start();
    writer.join();

    System.out.println("Running thread " + Thread.currentThread().getName());
    System.out.println(vm.a);
    System.out.println(vm.text);

   }

}

所以给出这个例子是否正确假设线程编写器对 "text" 的写入保证对读取它的任何其他线程可见?

作者似乎在利用变量 "a" 的易失性语义,并确保在刷新 "a" 时也会刷新对 "text" 的写入,是吗?保证?

我不认为是,但我自己的快速测试(以上)恰恰相反

你的想法。

is it correct to assume that the write to "text" by Thread writer is guaranteed to be visible by any other thread that reads it?

没有。但它保证在读取 text 之前被任何其他读取 a 的线程可见,就像您的示例一样:

  • text 的写入发生在写入线程 a 之前
  • writer 中 a 的写入发生在主线程中 a 的读取之前
  • happens-before 关系具有传递性
  • 因此 text 的写入发生在 a 的读取之前。

不,不能保证,因为"flushing"没那么简单。即使您实际上将非易失性内容写入 "main memory",也不能保证其他线程中的后续读取将从该主内存中读取它。考虑以下示例:

public class VolatileMain {

    private volatile int a = 0;
    private String text = "";

    public static void main(String[] args) throws Exception {

        VolatileMain vm = new VolatileMain();

        Thread writer = new Thread() {

            @Override
            public void run() {
                // Added sleep here, so waitForText method has chance to JIT-compile
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
                System.out.println("Running thread " + Thread.currentThread().getName());
                vm.text = "hello world";
                vm.a = 5;
                System.out.println("Text changed!");
            }
        };

        writer.start();

        waitForText(vm);

        writer.join();

        System.out.println("Running thread " + Thread.currentThread().getName());
        System.out.println(vm.a);
        System.out.println(vm.text);

    }

    // Wait for text change in the spin-loop
    private static void waitForText(VolatileMain vm) {
        int i = 0;
    /*
        @Edit by Soner
        Compiler may do following steps to optimize in lieu.
        String myCache = vm.text;
        -- Assume that here myCache is "" -- so stay forever.
        while (myCache.equals("")) { i++; }
    */
        while (vm.text.equals("")) {
            i++;
        }
        System.out.println("Wait complete: " + i);
    }
}

很有可能 waitForText 永远不会完成,只是因为 JIT 编译器会优化它并将 vm.text 的读取移出循环(因为它不是易变的,不volatile 读取在循环中执行并且 text 在循环内永远不会改变)使循环无限。

Volatile read/write 不仅会影响内存分配,还会改变 JIT 编译策略。在 while 循环中加入 vm.a 的读数,程序将正常运行。