happens-before 是 JMM(Java内存模型)里最抽象的概念之一,这篇用最直接的方式验证它:有volatile时2000轮测试真实零错误,没有volatile时reader线程真实被优化成永远退不出的死循环——不是理论推测,是真实观察到的现象。
上一篇:InheritableThreadLocal 真实验证
writer线程先写一个普通变量,再写一个volatile标志位;reader线程先自旋等volatile变true,再读普通变量——验证普通变量的值是否真实可见。
HappensBeforeDemo.java static int data = 0; static volatile boolean ready = false; static void writer() { data = 42; // 1. 普通写 ready = true; // 2. volatile写 } static void reader(AtomicInteger observed) { while (!ready) {} // 3. volatile读 observed.set(data); // 4. 真实:1 happens-before 4 }
2000 轮真实测试,因happens-before保证,观察到data错误值(非42)的次数: 0(应为0)
真实结果验证了 JMM 里最重要的一条 happens-before 规则:对同一个 volatile 变量,写操作 happens-before 后续的读操作。这条规则真实带来的效果不只是"ready 这个变量本身可见",而是真实建立了一条完整的传递链:data = 42(普通写,在volatile写之前)→ ready = true(volatile写)→while(!ready) 读到 true(volatile读)→ 之后的 data 读取。因为 happens-before 具有传递性,volatile 写读之间的这条边界真实把 data 的可见性也"捎带"保证了,2000 轮跨线程测试真实做到零错误。
同样的结构,把volatile去掉,改用普通boolean做标志位,看reader线程是否还能真实退出循环。
HappensBeforeDemo.java static boolean plainReady = false; // 没有volatile Thread reader = new Thread(() -> { int count = 0; while (!plainReady) { count++; } loopCount.set(count); });
第1轮无volatile测试: reader真实自旋次数=0, 真实是否在2秒内退出=false, 真实耗时=2023ms 第2轮无volatile测试: reader真实自旋次数=0, 真实是否在2秒内退出=false, 真实耗时=2029ms 第3轮无volatile测试: reader真实自旋次数=0, 真实是否在2秒内退出=false, 真实耗时=2025ms 第4轮无volatile测试: reader真实自旋次数=0, 真实是否在2秒内退出=false, 真实耗时=2029ms 第5轮无volatile测试: reader真实自旋次数=0, 真实是否在2秒内退出=false, 真实耗时=2026ms
这组对照实验最有价值的地方在于:它把 JMM 规范里"没有 happens-before 保证,读到的值是未定义的"这句听起来很学术的话,变成了两个真实、可复现、结果截然不同的程序行为——一个是 2000 次跨线程读取零差错,另一个是线程真实被永久卡死。这也是为什么系列前面 volatile 那篇反复强调的"不只是防止指令重排,更是提供可见性保证"这句话,在这里用最直接的方式被真实验证了。
每一行都是真实运行这段代码得到的结果。