我有一个关于java volatile read的非常困惑的问题。
我将展示两个案例来解释我的问题。
case1:
class TestVolatile {
public boolean running = true;
public volatile boolean volatileField;
void run() {
while(running) {
}
System.out.println("stopped.");
}
public static void main(String[] args) throws InterruptedException {
TestVolatile t = new TestVolatile ();
Thread t1 = new Thread(t::run, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
t.running = false;
t1.join();
}
}
case1不会停止,这是完全可以理解的,因为运行是不稳定的。
下面我将展示第二种情况。
例2:
class TestVolatile {
public boolean running = true;
public volatile boolean volatileField;
void run() {
while(running) {
// just add a volatile read, the code will stop .
if (volatileField) {
}
}
System.out.println("stopped.");
}
public static void main(String[] args) throws InterruptedException {
TestVolatile t = new TestVolatile ();
Thread t1 = new Thread(t::run, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
t.running = false;
t1.join();
}
}
如case2所示,在while循环中添加volatile read后,代码将停止。
所以,为什么?running不是volatile字段,代码也不写volatile字段。是否有一些之前发生的关系可以解释案例2?非常感谢您的帮助!
从JMM的角度来看,为什么?从本机代码的角度来看,为什么呢?
我增加了** -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*TestVolatile.run -XX:PrintAssemblyOptions=intel **到jvm选项查看TestVolatile.run的本机代码。但是我没有看到任何关于volatile read的特殊说明。
我的运行时:
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
macOS catalina 10.15.5
2.6 GHz 6-Core Intel Core i7
读取volatile字段不仅可以确保您看到它的最新版本。它还建立了一种正式的happens-before关系:对字段的任何写入都发生在随后对它的读取之前。这意味着读线程至少看到与写线程在写时看到的完整程序相同的状态。这就是导致读线程看到非易失性写的原因。
这些相同的语义也允许你跨线程发布非线程安全的对象,只要它们在发布后不被修改。它们与各种线程安全集合创建的语义相同,如java.util.concurrent
的文档中所述。
现在,在您的代码中,在将running
设置为false之后,您实际上并没有写入volatileField
。那么,happens-before边是从哪里来的呢?实际上——哪儿也没有!您仍然有数据竞争,并且JVM不必向您显示running
的最新版本。但是许多JVM实现只是将volatile read视为"好吧,让我们刷新所有核心缓存以确保这个线程看到所有内容"。你正从这种行为中获益。如果JVM使用的JMM比较吝啬和精确,可能会破坏您的代码。