Java Volatile与Happens-Before规则疑问:测试为何仍出现零值?
对volatile的happens-before规则的误解分析
问题描述
Java规范明确:对volatile字段的写入happens-before该字段的后续读取。但在并发场景中“后续”如何定义?编写了如下测试代码:
public class VolatileTest { static /*volatile*/ int counter = 0; static int zeros = 0; static int ones = 0; public static void main(String[] args) throws InterruptedException { int i = 0; while (i++ < 1_000_000) { if (i % 100_000 == 0) System.out.println(i); Thread thread1 = new Thread(VolatileTest::op1); Thread thread2 = new Thread(VolatileTest::op2); thread1.start(); thread2.start(); thread1.join(); thread2.join(); counter = 0; } System.out.println("Zeros " + zeros); System.out.println("Ones " + ones); } public static void op1() { counter = 1; } public static void op2() { if (counter == 0) ++zeros; if (counter == 1) ++ones; } }
未添加volatile时,输出会出现零值,符合预期;但添加volatile后,仍有零值出现,这似乎违背了规范中的规则。请问哪里理解错了?
解答
核心问题是你误解了**"后续读取"的定义**:这里的"后续"是指在程序的同步顺序(synchronization order)中处于写入操作之后的读取,而非线程启动顺序的先后,也不是代码编写顺序的先后。
你的测试代码存在两个关键问题:
- 线程调度无顺序保证:哪怕你先调用
thread1.start(),操作系统和JVM完全可能先调度thread2执行。thread2完全可以在thread1执行counter = 1之前就读取了counter的值,这时候读到的是初始值0,自然会让zeros计数增加。 - volatile不保证执行顺序:volatile的happens-before规则只做一件事——如果某个读取操作在同步顺序中确实排在写入操作之后,那这个读取一定能看到写入的最新值。但它不强制写入操作必须先于读取操作执行。
简单说:volatile解决的是"读的时候如果写已经完成,那能读到正确值"的问题,但解决不了"必须让写先发生在读之前"的问题。
如果要确保thread1的写入一定被thread2看到,你需要显式控制线程执行顺序,比如在启动thread2前先等待thread1完成:
thread1.start(); thread1.join(); // 先等thread1执行完写入 thread2.start(); thread2.join();
或者用CountDownLatch等同步工具来协调线程执行顺序,这样就能保证thread2的读取一定在thread1的写入之后,zeros就不会再增加了。
内容的提问来源于stack exchange,提问作者RedMurloc
相关产品推荐
相关产品推荐

