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

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

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”的写入,这是一个保证吗?

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

你的意见。

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

不会。但是,保证在阅读text之前读取text的任何其他线程都可以看到它,例如:

  • text的写入发生在写入线程中的a之前
  • 写入a作者发生 – 在主线程中读取a之前
  • 发生之前的关系是传递性的
  • 因此, text的写入发生在读取之前。

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

 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读取,程序将正常工作。