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

JCStress的异常结果是否由缓存而非指令重排序导致?

JCStress测试中的「0,1」结果成因分析

修改后的测试代码

@JCStressTest
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.")
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.")
@Outcome(id = "1, 0", expect = ACCEPTABLE, desc = "First is visible but not second.")
@Outcome(id = "0, 1", expect = ACCEPTABLE_INTERESTING, desc = "Second is visible but not first.")
@State
public class Reordering {
    int first;
    int second;

    @Actor
    public void actor1() {
        first = 1;
        second = 1;
    }

    @Actor
    public void actor2(II_Result r) {
        r.r2 = second;
        r.r1 = first;
    }
}

测试运行结果

结果采样数频率预期描述
0, 0737,822,06726.75%可接受提前执行两次读取。
0, 11,838,5780.07%值得关注second可见但first不可见。
1, 013,081,7010.47%可接受first可见但second不可见。
1, 12,005,604,40672.71%可接受延迟执行两次读取。

疑问点

常规可接受结果容易理解,但对「值得关注」的「0,1」结果存在疑问:已知JVM可能通过指令重排序,将actor1的逻辑近似转换为:

public void actor1() {
    second = 1;
    first = 1;
}

但不确定这个「0,1」结果是否有可能并非JVM指令重排序导致,而是因为未被volatile修饰的first字段被缓存在CPU寄存器/存储缓冲区中,未对执行actor2的线程可见所造成的?

解答

这个「0,1」结果的成因,既可能是JVM的指令重排序,也可能是CPU层面的缓存可见性问题,甚至是两者共同作用的结果——这两类场景本质上都属于Java内存模型(JMM)允许的「无序行为」范畴,因为JMM对普通字段的读写没有强制的顺序和可见性约束。

具体来说:

  • 如果是JVM层面的重排序:JIT编译器会在不违反单线程语义的前提下,调整first=1和second=1的执行顺序,让second的写入先完成,此时actor2读到second=1但first还没被赋值,就会出现「0,1」。
  • 如果是CPU缓存/存储缓冲区的问题:即使JVM没有重排序,CPU执行first=1后,这个值可能暂时存在存储缓冲区或本地缓存中,还没同步到主存;而second=1的写入更快完成并同步到主存,此时actor2从主存读到second=1,但从本地缓存/寄存器读到first=0,也会出现「0,1」。

实际上,JMM的设计就是要覆盖这两类场景:它不保证普通字段的读写顺序,也不保证写入对其他线程的即时可见性,所以这两种情况都是符合JMM规范的,都可能导致「0,1」的结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 13:30:42