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

写入volatile字段并读取另一volatile字段:happens-before规则是否有效?

关于volatile变量先行发生关系的疑问与验证

初始疑问

"对一个volatile字段的写入先行发生于后续对该字段或另一个volatile字段的读取"这一表述是否正确?

先行发生规则参考

  • 先行发生(Happens-before):两个操作可通过先行发生关系排序,若A先行发生于B,则A对B可见且执行顺序在B之前。
  • Volatile核心规则:对一个volatile字段的写入,仅先行发生于后续对该字段的读取。
  • Volatile可见性特性:线程写入volatile变量时,会将之前的所有变更同步至主内存,让其他线程能看到所有非volatile变量的最新值。

示例代码验证

示例1:非volatile字段无法保证可见性

这段代码无法保证控制台输出"thread end":

public class VolatileQuestion {

static boolean finish = false;

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        while (!finish);
        System.out.println("thread end");
    }).start();
    Thread.sleep(2000);
    finish = true;
    System.out.println("main end");
}
}

原因:非volatile的finish变量写入后,主线程的修改无法强制同步到主内存,子线程可能一直读取本地缓存的旧值,陷入死循环。

示例2:volatile字段直接保证可见性

将finish声明为volatile后,可保证输出"thread end":

public class VolatileQuestion {

static volatile boolean finish = false;

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        while (!finish);
        System.out.println("thread end");
    }).start();
    Thread.sleep(2000);
    finish = true;
    System.out.println("main end");
}
}

这里volatile的写入-读取规则生效:主线程对finish的写入先行发生于子线程对它的读取,子线程能及时感知到finish的最新值,退出循环。

示例3:同个volatile字段读写传递可见性

未将finish声明为volatile,但借助另一个volatile字段flush的读写,可保证输出"thread end":

public class VolatileQuestion {

static boolean finish = false;
static volatile boolean flush = false;

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        while (!finish) {
            boolean dummy = flush;
        }
        System.out.println("thread end");
    }).start();
    Thread.sleep(2000);
    finish = true;
    flush = true;
    System.out.println("main end");
}
}

逻辑链:主线程中finish = true按程序顺序先行发生于flush = true,而flush = true的写入先行发生于子线程对flush的读取(volatile规则),通过先行发生的传递性,finish的修改会被子线程感知。


核心技术问询:不同volatile字段的读写能否传递可见性?

如果主线程写入一个volatile字段,子线程读取另一个volatile字段,是否仍能保证输出"thread end"?对应代码:

public class VolatileQuestion {

static boolean finish = false;
static volatile boolean flush = false;
static volatile boolean refresh = false;

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        while (!finish) {
            boolean dummy = refresh;
        }
        System.out.println("thread end");
    }).start();
    Thread.sleep(2000);
    finish = true;
    flush = true;
    System.out.println("main end");
}
}

答案

首先明确:初始表述是错误的。Java内存模型中,volatile的先行发生规则仅针对同一个volatile字段,不同volatile字段的读写之间没有直接的先行发生关系。

回到核心代码:主线程写入flush = true,子线程读取refresh,这两个操作完全独立,没有任何规则能将它们的读写关联起来。虽然主线程中finish = true先行发生于flush = true,但flush和refresh是两个无关的volatile变量,无法通过传递性让子线程感知finish的修改。因此,子线程可能一直读取缓存中finish的旧值,无法退出循环,不能保证输出"thread end"。

只有当子线程读取的volatile字段和主线程写入的是同一个时,才能通过先行发生的传递性,让非volatile变量的修改被感知。

内容的提问来源于stack exchange,提问作者dibo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 09:15:01