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

时间:2015-11-02 06:58:27

标签: java concurrency jvm visibility volatile

所以我认为我已经足够了解这些东西,直到我读到让我怀疑这个主题的东西。我几乎可以肯定这本书是不正确的,但也想问社区。 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);

   }

}

所以给出这个例子,假设写入"文本"是正确的。通过线程编写器保证可以被任何其他读取它的线程看到吗?

似乎作者正在依赖于变量" a"的变体语义。并确保写入"文本"当" a"满脸通红,这是保证吗?

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

你的想法。

2 个答案:

答案 0 :(得分:4)

  

假设线程编写器对“text”的写入保证可以被读取它的任何其他线程看到是正确的吗?

没有。但是,保证在阅读a之前读取text的任何其他线程都可以看到它,如您的示例所示:

  • text的写入发生在编写器线程中写入a之前
  • 写作中a的写入发生在主线程中读取a之前
  • 之前发生的关系是及时的
  • 因此text的写入发生在阅读a之前。

答案 1 :(得分:4)

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

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的读数移出循环(因为它不易变形) ,循环中不执行易失性读取,并且text永远不会在循环内部发生变化)使循环无限。

易失性读/写不仅会影响内存承诺,还会改变JIT编译策略。在while循环中添加vm.a的读数,程序将正常工作。