You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:23:12