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

JCStress多读场景出现FORBIDDEN结果,请求技术分析

JCStress多读取场景下「1, 0」禁止结果的原因分析

问题场景

单写线程先给普通变量value赋值,再修改volatile修饰的state;两个读线程按「先读state、再读value」的顺序执行。单读时无异常,但多读场景下出现了state=1(已完成)但value=0(未记录结果)的禁止结果。

测试代码

public class RequestResultTest {

    @JCStressTest
    @Outcome(id = "1, 12345", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 12345", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.FORBIDDEN, desc = "State is finished, but not result recorded")
    @State
    public static class SingleWriterMultiReaderTest {

        private final SomeResult underTest;

        public SingleWriterMultiReaderTest() {
            this.underTest = new SomeResult();
        }

        @Actor
        public void finish() {
            underTest.finish(12345L);
        }

        @Actor
        public void getStateAndResult1(IJ_Result result) {
            int state = underTest.getState();
            long longResult = underTest.getValue();

            result.r1 = state;
            result.r2 = longResult;
        }

        @Actor
        public void getStateAndResult2(IJ_Result result) {
            int state = underTest.getState();
            long longResult = underTest.getValue();

            result.r1 = state;
            result.r2 = longResult;
        }
    }

    public static final class SomeResult {

        public static final int RUNNING = 0;
        public static final int FINISHED = 1;

        private volatile int state;
        private long value;

        public SomeResult() {
            this.value = 0L;
            this.state = RUNNING;
        }

        void finish(long value) {
            this.value = value;
            this.state = FINISHED;
        }

        public long getValue() {
            return value;
        }

        public int getState() {
            return state;
        }
    }
}

测试输出

Compilation: split
    finish: Interpreter
    getStateAndResult1: C1
    getStateAndResult2: C2

  JVM args: [-Dfile.encoding=UTF-8, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=1773949580]
  Fork: #1

    RESULT      SAMPLES     FREQ      EXPECT  DESCRIPTION
      0, 0  142,382,593   97,91%  Acceptable  
  0, 12345      116,937    0,08%  Acceptable  
      1, 0        7,940   <0,01%   Forbidden  State is finished, but not result recorded
  1, 12345    2,908,741    2,00%  Acceptable  

原因拆解

1. 编译器激进优化打破顺序预期

写线程中this.value = 12345(普通写)程序顺序早于this.state = FINISHED(volatile写),且JMM规则禁止普通写被重排到volatile写之后,写线程内部操作顺序是可靠的。

但C2全优化编译器会做推测加载:读线程可能提前读取value(普通变量读取无副作用),之后才读取volatile的state。如果在推测读取value=0后,写线程完成了value和state的赋值,读线程最终会返回state=1和提前读取的value=0,形成禁止结果。

2. 多线程编译层次差异放大问题

测试输出显示写线程在解释器执行,两个读线程分别由C1、C2编译。C2的优化强度远高于C1,单读场景下编译器优化较弱,因此未触发该问题;多读场景下C2的激进优化导致了异常结果。

修复方案

方案1:将value声明为volatile

让value的读/写都具备volatile语义,禁止推测加载,同时保证多线程可见性:

private volatile long value;

方案2:用同步块保证原子性与可见性

在finish方法中加入synchronized,确保写操作原子性,通过锁的happens-before规则保证读线程能看到最新值:

void finish(long value) {
    synchronized(this) {
        this.value = value;
        this.state = FINISHED;
    }
}

方案3:在getValue中依赖volatile读语义

先读取volatile的state,强制建立happens-before关系,禁止value的读被重排到state读之前:

public long getValue() {
    // 先读volatile变量,触发缓存刷新
    int s = state;
    return value;
}

方案4:用不可变对象替换普通变量

将value和state封装为不可变对象,通过volatile引用更新,保证读线程看到的是完整的状态:

public static final class SomeResult {
    public static final int RUNNING = 0;
    public static final int FINISHED = 1;

    private volatile ResultHolder holder;

    public SomeResult() {
        this.holder = new ResultHolder(RUNNING, 0L);
    }

    void finish(long value) {
        this.holder = new ResultHolder(FINISHED, value);
    }

    public long getValue() {
        return holder.value;
    }

    public int getState() {
        return holder.state;
    }

    private static class ResultHolder {
        public final int state;
        public final long value;

        ResultHolder(int state, long value) {
            this.state = state;
            this.value = value;
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:44:50