x86架构下Java happens-before实现与字段可见性异常问题咨询
嘿,这两个问题都是Java并发里关于可见性的经典场景,我来给你逐个拆解清楚:
问题1:Java happens-before在x86架构中的实现里,线程t1会写入主内存吗?
首先得明确:Java的happens-before是抽象的内存模型约束,而x86架构的内存模型(TSO,Total Store Order)本身就属于强一致性模型,大部分场景下天然符合JMM的要求,但具体会不会刷主内存,得看具体操作:
- 如果t1写入的是volatile变量:根据JMM规则,volatile写会触发写屏障。在x86上,JVM会插入带
LOCK前缀的指令(比如LOCK ADDL $0,(%RSP),一个空操作但带锁前缀),这个指令会强制把当前CPU缓存的修改刷到主内存,同时让其他CPU的对应缓存行失效,确保后续其他线程的读取能拿到最新值。 - 如果t1写入的是普通变量:JMM没有强制要求立刻刷主内存,x86的TSO也允许CPU把写操作暂存在本地缓存里。但happens-before规则的核心是保证顺序可见性,而不是强制立刻刷主存。比如如果t1的写之后有释放锁的操作,t2的读之前有获取同一个锁的操作,那锁的happens-before链会保证t1的写对t2可见——这时候锁的
LOCK指令会间接把之前的写操作刷到主内存。
总结来说:不是线程t1的所有写操作都会直接刷主内存,要看变量类型和后续的同步操作,但happens-before的保证在x86上是通过架构本身的TSO特性,加上JVM插入的内存屏障来实现的。
问题2:为什么打开println注释后,读取线程能看到stop的更新?
先还原你的场景:stop是普通boolean变量,原本读线程一直在本地缓存里读取旧值,看不到写线程的修改。而加了println之后,循环居然终止了——这背后的关键藏在System.out.println()的实现里!
你去翻JDK源码的话,会发现PrintStream.println()方法是带同步锁的:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
当读线程执行println时,会进入synchronized块,而根据JMM的监视器锁规则:
- 退出同步块(解锁操作)happens-before后续对同一个锁的加锁操作;
- 进入同步块时,JVM会强制触发本地缓存刷新(相当于读屏障),把主内存的最新值加载到当前CPU的本地缓存;
- 退出同步块时,会把本地缓存的修改刷到主内存(写屏障)。
所以,每次读线程执行println,都会触发一次缓存刷新,这时候就会去主内存读取stop的最新值。而写线程虽然一开始只是把stop写到自己的本地缓存,但x86的TSO模型下,写操作最终会被异步刷到主内存(只是时间不确定),当读线程触发了缓存刷新,就拿到了更新后的stop值,循环自然就终止了。
补充一句:这个现象不是x86特有的,在其他架构上也会出现,本质是synchronized带来的内存可见性保证,x86的强内存模型只是让这个过程表现得更“及时”而已。
内容的提问来源于stack exchange,提问作者Senthil Raja

